Changeset 15217 in josm


Ignore:
Timestamp:
2019-07-07T01:56:46+02:00 (7 weeks ago)
Author:
Don-vip
Message:

see #17848 - update to metadata-extractor 2.12.0

Location:
trunk/src/com/drew
Files:
3 added
3 deleted
120 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/com/drew/imaging/ImageProcessingException.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/imaging/PhotographicConversions.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/imaging/jpeg/JpegMetadataReader.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3535//import com.drew.metadata.adobe.AdobeJpegReader;
    3636import com.drew.metadata.exif.ExifReader;
    37 import com.drew.metadata.file.FileMetadataReader;
     37import com.drew.metadata.file.FileSystemMetadataReader;
    3838//import com.drew.metadata.icc.IccReader;
    3939import com.drew.metadata.iptc.IptcReader;
     
    9595            inputStream.close();
    9696        }
    97         new FileMetadataReader().read(file, metadata);
     97        new FileSystemMetadataReader().read(file, metadata);
    9898        return metadata;
    9999    }
  • trunk/src/com/drew/imaging/jpeg/JpegProcessingException.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/imaging/jpeg/JpegSegmentData.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/imaging/jpeg/JpegSegmentReader.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    153153                segmentData.addSegment(segmentType, segmentBytes);
    154154            } else {
    155                 // Some if the JPEG is truncated, just return what data we've already gathered
     155                // Skip this segment
    156156                if (!reader.trySkip(segmentLength)) {
     157                    // If skipping failed, just return the segments we found so far
    157158                    return segmentData;
    158159                }
  • trunk/src/com/drew/imaging/jpeg/JpegSegmentType.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/imaging/tiff/TiffDataFormat.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/imaging/tiff/TiffHandler.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/imaging/tiff/TiffProcessingException.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/imaging/tiff/TiffReader.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/BufferBoundsException.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/ByteArrayReader.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/Charsets.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3838    public static final Charset UTF_16BE = Charset.forName("UTF-16BE");
    3939    public static final Charset UTF_16LE = Charset.forName("UTF-16LE");
     40    public static final Charset WINDOWS_1252 = Charset.forName("Cp1252");
    4041}
  • trunk/src/com/drew/lang/CompoundException.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/GeoLocation.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/RandomAccessReader.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2222package com.drew.lang;
    2323
     24import com.drew.lang.annotations.NotNull;
     25import com.drew.lang.annotations.Nullable;
     26import com.drew.metadata.StringValue;
     27
    2428import java.io.IOException;
    2529import java.io.UnsupportedEncodingException;
    2630import java.nio.charset.Charset;
    27 
    28 import com.drew.lang.annotations.NotNull;
    29 import com.drew.lang.annotations.Nullable;
    30 import com.drew.metadata.StringValue;
    3131
    3232/**
  • trunk/src/com/drew/lang/Rational.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/SequentialByteArrayReader.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/SequentialReader.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/StreamReader.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/StringUtil.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/annotations/NotNull.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/annotations/Nullable.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/Age.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/Directory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    757757        if (o == null)
    758758            return null;
     759        if (o instanceof Number)
     760            return ((Number)o).longValue();
    759761        if (o instanceof String || o instanceof StringValue) {
    760762            try {
     
    763765                return null;
    764766            }
    765         }
    766         if (o instanceof Number)
    767             return ((Number)o).longValue();
     767        } else if (o instanceof Rational[]) {
     768            Rational[] rationals = (Rational[])o;
     769            if (rationals.length == 1)
     770                return rationals[0].longValue();
     771        } else if (o instanceof byte[]) {
     772            byte[] bytes = (byte[])o;
     773            if (bytes.length == 1)
     774                return (long)bytes[0];
     775        } else if (o instanceof int[]) {
     776            int[] ints = (int[])o;
     777            if (ints.length == 1)
     778                return (long)ints[0];
     779        } else if (o instanceof short[]) {
     780            short[] shorts = (short[])o;
     781            if (shorts.length == 1)
     782                return (long)shorts[0];
     783        }
    768784        return null;
    769785    }
     
    10091025                }
    10101026            } else if (componentType.getName().equals("float")) {
     1027                DecimalFormat format = new DecimalFormat(_floatFormatPattern);
    10111028                for (int i = 0; i < arrayLength; i++) {
    10121029                    if (i != 0)
    10131030                        string.append(' ');
    1014                     string.append(new DecimalFormat(_floatFormatPattern).format(Array.getFloat(o, i)));
     1031                    String s = format.format(Array.getFloat(o, i));
     1032                    string.append(s.equals("-0") ? "0" : s);
    10151033                }
    10161034            } else if (componentType.getName().equals("double")) {
     1035                DecimalFormat format = new DecimalFormat(_floatFormatPattern);
    10171036                for (int i = 0; i < arrayLength; i++) {
    10181037                    if (i != 0)
    10191038                        string.append(' ');
    1020                     string.append(new DecimalFormat(_floatFormatPattern).format(Array.getDouble(o, i)));
     1039                    String s = format.format(Array.getDouble(o, i));
     1040                    string.append(s.equals("-0") ? "0" : s);
    10211041                }
    10221042            } else if (componentType.getName().equals("byte")) {
  • trunk/src/com/drew/metadata/ErrorDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/Face.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/Metadata.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/MetadataException.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/StringValue.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/Tag.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/TagDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3434import java.util.ArrayList;
    3535import java.util.Date;
     36import java.util.HashMap;
    3637import java.util.List;
     38import java.util.Map;
    3739
    3840/**
     
    7981        }
    8082
    81         if (object instanceof Date)
    82         {
     83        if (object instanceof Date) {
    8384            // Produce a date string having a format that includes the offset in form "+00:00"
    8485            return new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy")
     
    140141    protected String getIndexedDescription(final int tagType, final int baseIndex, @NotNull String... descriptions)
    141142    {
    142         final Integer index = _directory.getInteger(tagType);
     143        final Long index = _directory.getLongObject(tagType);
    143144        if (index == null)
    144145            return null;
    145         final int arrayIndex = index - baseIndex;
     146        final long arrayIndex = index - baseIndex;
    146147        if (arrayIndex >= 0 && arrayIndex < descriptions.length) {
    147             String description = descriptions[arrayIndex];
     148            String description = descriptions[(int)arrayIndex];
    148149            if (description != null)
    149150                return description;
     
    211212        // TODO have observed a byte[8] here which is likely some kind of date (ticks as long?)
    212213        Long value = _directory.getLongObject(tagType);
    213         if (value==null)
     214        if (value == null)
    214215            return null;
    215216        return new Date(value).toString();
     
    227228            return null;
    228229
    229         List<String> parts = new ArrayList<String>();
     230        List<String> parts = new ArrayList<>();
    230231
    231232        int bitIndex = 0;
     
    292293
    293294        Double d = _directory.getDoubleObject(tagType);
    294         if (d != null)
    295         {
     295        if (d != null) {
    296296            DecimalFormat format = new DecimalFormat("0.###");
    297297            return format.format(d);
     
    379379        if (apexValue <= 1) {
    380380            float apexPower = (float)(1 / (Math.exp(apexValue * Math.log(2))));
    381             long apexPower10 = Math.round((double)apexPower * 10.0);
    382             float fApexPower = (float)apexPower10 / 10.0f;
     381            long apexPower10 = Math.round(apexPower * 10.0);
     382            float fApexPower = apexPower10 / 10.0f;
    383383            DecimalFormat format = new DecimalFormat("0.##");
    384384            format.setRoundingMode(RoundingMode.HALF_UP);
     
    414414    protected String getLightSourceDescription(short wbtype)
    415415    {
    416         switch (wbtype)
    417         {
     416        switch (wbtype) {
    418417            case 0:
    419418                return "Unknown";
     
    464463        return getDescription(wbtype);
    465464    }
     465
     466    // EXIF UserComment, GPSProcessingMethod and GPSAreaInformation
     467    @Nullable
     468    protected String getEncodedTextDescription(int tagType)
     469    {
     470        byte[] commentBytes = _directory.getByteArray(tagType);
     471        if (commentBytes == null)
     472            return null;
     473        if (commentBytes.length == 0)
     474            return "";
     475
     476        final Map<String, String> encodingMap = new HashMap<>();
     477        encodingMap.put("ASCII", System.getProperty("file.encoding")); // Someone suggested "ISO-8859-1".
     478        encodingMap.put("UNICODE", "UTF-16LE");
     479        encodingMap.put("JIS", "Shift-JIS"); // We assume this charset for now.  Another suggestion is "JIS".
     480
     481        try {
     482            if (commentBytes.length >= 10) {
     483                String firstTenBytesString = new String(commentBytes, 0, 10);
     484
     485                // try each encoding name
     486                for (Map.Entry<String, String> pair : encodingMap.entrySet()) {
     487                    String encodingName = pair.getKey();
     488                    String charset = pair.getValue();
     489                    if (firstTenBytesString.startsWith(encodingName)) {
     490                        // skip any null or blank characters commonly present after the encoding name, up to a limit of 10 from the start
     491                        for (int j = encodingName.length(); j < 10; j++) {
     492                            byte b = commentBytes[j];
     493                            if (b != '\0' && b != ' ')
     494                                return new String(commentBytes, j, commentBytes.length - j, charset).trim();
     495                        }
     496                        return new String(commentBytes, 10, commentBytes.length - 10, charset).trim();
     497                    }
     498                }
     499            }
     500            // special handling fell through, return a plain string representation
     501            return new String(commentBytes, System.getProperty("file.encoding")).trim();
     502        } catch (UnsupportedEncodingException ex) {
     503            return null;
     504        }
     505    }
    466506}
  • trunk/src/com/drew/metadata/exif/ExifDescriptorBase.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3333import java.io.UnsupportedEncodingException;
    3434import java.text.DecimalFormat;
    35 import java.util.HashMap;
    36 import java.util.Map;
    3735
    3836import static com.drew.metadata.exif.ExifDirectoryBase.*;
     
    6765    public String getDescription(int tagType)
    6866    {
    69         // TODO order case blocks and corresponding methods in the same order as the TAG_* values are defined
    70 
    7167        switch (tagType) {
    7268            case TAG_INTEROP_INDEX:
     
    7470            case TAG_INTEROP_VERSION:
    7571                return getInteropVersionDescription();
    76             case TAG_ORIENTATION:
    77                 return getOrientationDescription();
    78             case TAG_RESOLUTION_UNIT:
    79                 return getResolutionDescription();
    80             case TAG_YCBCR_POSITIONING:
    81                 return getYCbCrPositioningDescription();
    82             case TAG_X_RESOLUTION:
    83                 return getXResolutionDescription();
    84             case TAG_Y_RESOLUTION:
    85                 return getYResolutionDescription();
     72            case TAG_NEW_SUBFILE_TYPE:
     73                return getNewSubfileTypeDescription();
     74            case TAG_SUBFILE_TYPE:
     75                return getSubfileTypeDescription();
    8676            case TAG_IMAGE_WIDTH:
    8777                return getImageWidthDescription();
     
    9080            case TAG_BITS_PER_SAMPLE:
    9181                return getBitsPerSampleDescription();
     82            case TAG_COMPRESSION:
     83                return getCompressionDescription();
    9284            case TAG_PHOTOMETRIC_INTERPRETATION:
    9385                return getPhotometricInterpretationDescription();
     86            case TAG_THRESHOLDING:
     87                return getThresholdingDescription();
     88            case TAG_FILL_ORDER:
     89                return getFillOrderDescription();
     90            case TAG_ORIENTATION:
     91                return getOrientationDescription();
     92            case TAG_SAMPLES_PER_PIXEL:
     93                return getSamplesPerPixelDescription();
    9494            case TAG_ROWS_PER_STRIP:
    9595                return getRowsPerStripDescription();
    9696            case TAG_STRIP_BYTE_COUNTS:
    9797                return getStripByteCountsDescription();
    98             case TAG_SAMPLES_PER_PIXEL:
    99                 return getSamplesPerPixelDescription();
     98            case TAG_X_RESOLUTION:
     99                return getXResolutionDescription();
     100            case TAG_Y_RESOLUTION:
     101                return getYResolutionDescription();
    100102            case TAG_PLANAR_CONFIGURATION:
    101103                return getPlanarConfigurationDescription();
     104            case TAG_RESOLUTION_UNIT:
     105                return getResolutionDescription();
     106            case TAG_JPEG_PROC:
     107                return getJpegProcDescription();
    102108            case TAG_YCBCR_SUBSAMPLING:
    103109                return getYCbCrSubsamplingDescription();
     110            case TAG_YCBCR_POSITIONING:
     111                return getYCbCrPositioningDescription();
    104112            case TAG_REFERENCE_BLACK_WHITE:
    105113                return getReferenceBlackWhiteDescription();
    106             case TAG_WIN_AUTHOR:
    107                 return getWindowsAuthorDescription();
    108             case TAG_WIN_COMMENT:
    109                 return getWindowsCommentDescription();
    110             case TAG_WIN_KEYWORDS:
    111                 return getWindowsKeywordsDescription();
    112             case TAG_WIN_SUBJECT:
    113                 return getWindowsSubjectDescription();
    114             case TAG_WIN_TITLE:
    115                 return getWindowsTitleDescription();
    116             case TAG_NEW_SUBFILE_TYPE:
    117                 return getNewSubfileTypeDescription();
    118             case TAG_SUBFILE_TYPE:
    119                 return getSubfileTypeDescription();
    120             case TAG_THRESHOLDING:
    121                 return getThresholdingDescription();
    122             case TAG_FILL_ORDER:
    123                 return getFillOrderDescription();
    124114            case TAG_CFA_PATTERN_2:
    125115                return getCfaPattern2Description();
    126116            case TAG_EXPOSURE_TIME:
    127117                return getExposureTimeDescription();
     118            case TAG_FNUMBER:
     119                return getFNumberDescription();
     120            case TAG_EXPOSURE_PROGRAM:
     121                return getExposureProgramDescription();
     122            case TAG_ISO_EQUIVALENT:
     123                return getIsoEquivalentDescription();
     124            case TAG_SENSITIVITY_TYPE:
     125                return getSensitivityTypeRangeDescription();
     126            case TAG_EXIF_VERSION:
     127                return getExifVersionDescription();
     128            case TAG_COMPONENTS_CONFIGURATION:
     129                return getComponentConfigurationDescription();
     130            case TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL:
     131                return getCompressedAverageBitsPerPixelDescription();
    128132            case TAG_SHUTTER_SPEED:
    129133                return getShutterSpeedDescription();
    130             case TAG_FNUMBER:
    131                 return getFNumberDescription();
    132             case TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL:
    133                 return getCompressedAverageBitsPerPixelDescription();
     134            case TAG_APERTURE:
     135                return getApertureValueDescription();
     136            case TAG_BRIGHTNESS_VALUE:
     137                return getBrightnessValueDescription();
     138            case TAG_EXPOSURE_BIAS:
     139                return getExposureBiasDescription();
     140            case TAG_MAX_APERTURE:
     141                return getMaxApertureValueDescription();
    134142            case TAG_SUBJECT_DISTANCE:
    135143                return getSubjectDistanceDescription();
     
    142150            case TAG_FOCAL_LENGTH:
    143151                return getFocalLengthDescription();
     152            case TAG_USER_COMMENT:
     153                return getUserCommentDescription();
     154            case TAG_TEMPERATURE:
     155                return getTemperatureDescription();
     156            case TAG_HUMIDITY:
     157                return getHumidityDescription();
     158            case TAG_PRESSURE:
     159                return getPressureDescription();
     160            case TAG_WATER_DEPTH:
     161                return getWaterDepthDescription();
     162            case TAG_ACCELERATION:
     163                return getAccelerationDescription();
     164            case TAG_CAMERA_ELEVATION_ANGLE:
     165                return getCameraElevationAngleDescription();
     166            case TAG_WIN_TITLE:
     167                return getWindowsTitleDescription();
     168            case TAG_WIN_COMMENT:
     169                return getWindowsCommentDescription();
     170            case TAG_WIN_AUTHOR:
     171                return getWindowsAuthorDescription();
     172            case TAG_WIN_KEYWORDS:
     173                return getWindowsKeywordsDescription();
     174            case TAG_WIN_SUBJECT:
     175                return getWindowsSubjectDescription();
     176            case TAG_FLASHPIX_VERSION:
     177                return getFlashPixVersionDescription();
    144178            case TAG_COLOR_SPACE:
    145179                return getColorSpaceDescription();
     
    148182            case TAG_EXIF_IMAGE_HEIGHT:
    149183                return getExifImageHeightDescription();
    150             case TAG_FOCAL_PLANE_RESOLUTION_UNIT:
    151                 return getFocalPlaneResolutionUnitDescription();
    152184            case TAG_FOCAL_PLANE_X_RESOLUTION:
    153185                return getFocalPlaneXResolutionDescription();
    154186            case TAG_FOCAL_PLANE_Y_RESOLUTION:
    155187                return getFocalPlaneYResolutionDescription();
    156             case TAG_EXPOSURE_PROGRAM:
    157                 return getExposureProgramDescription();
    158             case TAG_APERTURE:
    159                 return getApertureValueDescription();
    160             case TAG_MAX_APERTURE:
    161                 return getMaxApertureValueDescription();
     188            case TAG_FOCAL_PLANE_RESOLUTION_UNIT:
     189                return getFocalPlaneResolutionUnitDescription();
    162190            case TAG_SENSING_METHOD:
    163191                return getSensingMethodDescription();
    164             case TAG_EXPOSURE_BIAS:
    165                 return getExposureBiasDescription();
    166192            case TAG_FILE_SOURCE:
    167193                return getFileSourceDescription();
     
    170196            case TAG_CFA_PATTERN:
    171197                return getCfaPatternDescription();
    172             case TAG_COMPONENTS_CONFIGURATION:
    173                 return getComponentConfigurationDescription();
    174             case TAG_EXIF_VERSION:
    175                 return getExifVersionDescription();
    176             case TAG_FLASHPIX_VERSION:
    177                 return getFlashPixVersionDescription();
    178             case TAG_ISO_EQUIVALENT:
    179                 return getIsoEquivalentDescription();
    180             case TAG_USER_COMMENT:
    181                 return getUserCommentDescription();
    182198            case TAG_CUSTOM_RENDERED:
    183199                return getCustomRenderedDescription();
     
    202218            case TAG_SUBJECT_DISTANCE_RANGE:
    203219                return getSubjectDistanceRangeDescription();
    204             case TAG_SENSITIVITY_TYPE:
    205                 return getSensitivityTypeRangeDescription();
    206             case TAG_COMPRESSION:
    207                 return getCompressionDescription();
    208             case TAG_JPEG_PROC:
    209                 return getJpegProcDescription();
    210220            case TAG_LENS_SPECIFICATION:
    211221                return getLensSpecificationDescription();
     
    216226
    217227    @Nullable
    218     public String getInteropVersionDescription()
    219     {
    220         return getVersionBytesDescription(TAG_INTEROP_VERSION, 2);
    221     }
    222 
    223     @Nullable
    224228    public String getInteropIndexDescription()
    225229    {
     
    235239
    236240    @Nullable
    237     public String getReferenceBlackWhiteDescription()
    238     {
    239         // For some reason, sometimes this is read as a long[] and
    240         // getIntArray isn't able to deal with it
    241         int[] ints = _directory.getIntArray(TAG_REFERENCE_BLACK_WHITE);
    242         if (ints==null || ints.length < 6)
    243         {
    244             Object o = _directory.getObject(TAG_REFERENCE_BLACK_WHITE);
    245             if (o != null && (o instanceof long[]))
    246             {
    247                 long[] longs = (long[])o;
    248                 if (longs.length < 6)
    249                     return null;
    250 
    251                 ints = new int[longs.length];
    252                 for (int i = 0; i < longs.length; i++)
    253                     ints[i] = (int)longs[i];
    254             }
    255             else
    256                 return null;
    257         }
    258 
    259         int blackR = ints[0];
    260         int whiteR = ints[1];
    261         int blackG = ints[2];
    262         int whiteG = ints[3];
    263         int blackB = ints[4];
    264         int whiteB = ints[5];
    265         return String.format("[%d,%d,%d] [%d,%d,%d]", blackR, blackG, blackB, whiteR, whiteG, whiteB);
    266     }
    267 
    268     @Nullable
    269     public String getYResolutionDescription()
    270     {
    271         Rational value = _directory.getRational(TAG_Y_RESOLUTION);
    272         if (value==null)
    273             return null;
    274         final String unit = getResolutionDescription();
    275         return String.format("%s dots per %s",
    276             value.toSimpleString(_allowDecimalRepresentationOfRationals),
    277             unit == null ? "unit" : unit.toLowerCase());
    278     }
    279 
    280     @Nullable
    281     public String getXResolutionDescription()
    282     {
    283         Rational value = _directory.getRational(TAG_X_RESOLUTION);
    284         if (value == null)
    285             return null;
    286         final String unit = getResolutionDescription();
    287         return String.format("%s dots per %s",
    288             value.toSimpleString(_allowDecimalRepresentationOfRationals),
    289             unit == null ? "unit" : unit.toLowerCase());
    290     }
    291 
    292     @Nullable
    293     public String getYCbCrPositioningDescription()
    294     {
    295         return getIndexedDescription(TAG_YCBCR_POSITIONING, 1, "Center of pixel array", "Datum point");
    296     }
    297 
    298     @Nullable
    299     public String getOrientationDescription()
    300     {
    301         return super.getOrientationDescription(TAG_ORIENTATION);
    302     }
    303 
    304     @Nullable
    305     public String getResolutionDescription()
    306     {
    307         // '1' means no-unit, '2' means inch, '3' means centimeter. Default value is '2'(inch)
    308         return getIndexedDescription(TAG_RESOLUTION_UNIT, 1, "(No unit)", "Inch", "cm");
    309     }
    310 
    311     /** The Windows specific tags uses plain Unicode. */
    312     @Nullable
    313     private String getUnicodeDescription(int tag)
    314     {
    315         byte[] bytes = _directory.getByteArray(tag);
    316         if (bytes == null)
    317             return null;
    318         try {
    319             // Decode the unicode string and trim the unicode zero "\0" from the end.
    320             return new String(bytes, "UTF-16LE").trim();
    321         } catch (UnsupportedEncodingException ex) {
    322             return null;
    323         }
    324     }
    325 
    326     @Nullable
    327     public String getWindowsAuthorDescription()
    328     {
    329         return getUnicodeDescription(TAG_WIN_AUTHOR);
    330     }
    331 
    332     @Nullable
    333     public String getWindowsCommentDescription()
    334     {
    335         return getUnicodeDescription(TAG_WIN_COMMENT);
    336     }
    337 
    338     @Nullable
    339     public String getWindowsKeywordsDescription()
    340     {
    341         return getUnicodeDescription(TAG_WIN_KEYWORDS);
    342     }
    343 
    344     @Nullable
    345     public String getWindowsTitleDescription()
    346     {
    347         return getUnicodeDescription(TAG_WIN_TITLE);
    348     }
    349 
    350     @Nullable
    351     public String getWindowsSubjectDescription()
    352     {
    353         return getUnicodeDescription(TAG_WIN_SUBJECT);
    354     }
    355 
    356     @Nullable
    357     public String getYCbCrSubsamplingDescription()
    358     {
    359         int[] positions = _directory.getIntArray(TAG_YCBCR_SUBSAMPLING);
    360         if (positions == null || positions.length < 2)
    361             return null;
    362         if (positions[0] == 2 && positions[1] == 1) {
    363             return "YCbCr4:2:2";
    364         } else if (positions[0] == 2 && positions[1] == 2) {
    365             return "YCbCr4:2:0";
    366         } else {
    367             return "(Unknown)";
    368         }
    369     }
    370 
    371     @Nullable
    372     public String getPlanarConfigurationDescription()
    373     {
    374         // When image format is no compression YCbCr, this value shows byte aligns of YCbCr
    375         // data. If value is '1', Y/Cb/Cr value is chunky format, contiguous for each subsampling
    376         // pixel. If value is '2', Y/Cb/Cr value is separated and stored to Y plane/Cb plane/Cr
    377         // plane format.
    378         return getIndexedDescription(TAG_PLANAR_CONFIGURATION,
    379             1,
    380             "Chunky (contiguous for each subsampling pixel)",
    381             "Separate (Y-plane/Cb-plane/Cr-plane format)"
    382         );
    383     }
    384 
    385     @Nullable
    386     public String getSamplesPerPixelDescription()
    387     {
    388         String value = _directory.getString(TAG_SAMPLES_PER_PIXEL);
    389         return value == null ? null : value + " samples/pixel";
    390     }
    391 
    392     @Nullable
    393     public String getRowsPerStripDescription()
    394     {
    395         final String value = _directory.getString(TAG_ROWS_PER_STRIP);
    396         return value == null ? null : value + " rows/strip";
    397     }
    398 
    399     @Nullable
    400     public String getStripByteCountsDescription()
    401     {
    402         final String value = _directory.getString(TAG_STRIP_BYTE_COUNTS);
    403         return value == null ? null : value + " bytes";
    404     }
    405 
    406     @Nullable
    407     public String getPhotometricInterpretationDescription()
    408     {
    409         // Shows the color space of the image data components
    410         Integer value = _directory.getInteger(TAG_PHOTOMETRIC_INTERPRETATION);
    411         if (value == null)
    412             return null;
    413         switch (value) {
    414             case 0: return "WhiteIsZero";
    415             case 1: return "BlackIsZero";
    416             case 2: return "RGB";
    417             case 3: return "RGB Palette";
    418             case 4: return "Transparency Mask";
    419             case 5: return "CMYK";
    420             case 6: return "YCbCr";
    421             case 8: return "CIELab";
    422             case 9: return "ICCLab";
    423             case 10: return "ITULab";
    424             case 32803: return "Color Filter Array";
    425             case 32844: return "Pixar LogL";
    426             case 32845: return "Pixar LogLuv";
    427             case 32892: return "Linear Raw";
    428             default:
    429                 return "Unknown colour space";
    430         }
    431     }
    432 
    433     @Nullable
    434     public String getBitsPerSampleDescription()
    435     {
    436         String value = _directory.getString(TAG_BITS_PER_SAMPLE);
    437         return value == null ? null : value + " bits/component/pixel";
    438     }
    439 
    440     @Nullable
    441     public String getImageWidthDescription()
    442     {
    443         String value = _directory.getString(TAG_IMAGE_WIDTH);
    444         return value == null ? null : value + " pixels";
    445     }
    446 
    447     @Nullable
    448     public String getImageHeightDescription()
    449     {
    450         String value = _directory.getString(TAG_IMAGE_HEIGHT);
    451         return value == null ? null : value + " pixels";
     241    public String getInteropVersionDescription()
     242    {
     243        return getVersionBytesDescription(TAG_INTEROP_VERSION, 2);
    452244    }
    453245
     
    478270
    479271    @Nullable
    480     public String getThresholdingDescription()
    481     {
    482         return getIndexedDescription(TAG_THRESHOLDING, 1,
    483             "No dithering or halftoning",
    484             "Ordered dither or halftone",
    485             "Randomized dither"
    486         );
    487     }
    488 
    489     @Nullable
    490     public String getFillOrderDescription()
    491     {
    492         return getIndexedDescription(TAG_FILL_ORDER, 1,
    493             "Normal",
    494             "Reversed"
    495         );
    496     }
    497 
    498     @Nullable
    499     public String getSubjectDistanceRangeDescription()
    500     {
    501         return getIndexedDescription(TAG_SUBJECT_DISTANCE_RANGE,
    502             "Unknown",
    503             "Macro",
    504             "Close view",
    505             "Distant view"
    506         );
    507     }
    508 
    509     @Nullable
    510     public String getSensitivityTypeRangeDescription()
    511     {
    512         return getIndexedDescription(TAG_SENSITIVITY_TYPE,
    513             "Unknown",
    514             "Standard Output Sensitivity",
    515             "Recommended Exposure Index",
    516             "ISO Speed",
    517             "Standard Output Sensitivity and Recommended Exposure Index",
    518             "Standard Output Sensitivity and ISO Speed",
    519             "Recommended Exposure Index and ISO Speed",
    520             "Standard Output Sensitivity, Recommended Exposure Index and ISO Speed"
    521         );
    522     }
    523 
    524     @Nullable
    525     public String getLensSpecificationDescription()
    526     {
    527         return getLensSpecificationDescription(TAG_LENS_SPECIFICATION);
    528     }
    529 
    530     @Nullable
    531     public String getSharpnessDescription()
    532     {
    533         return getIndexedDescription(TAG_SHARPNESS,
    534             "None",
    535             "Low",
    536             "Hard"
    537         );
    538     }
    539 
    540     @Nullable
    541     public String getSaturationDescription()
    542     {
    543         return getIndexedDescription(TAG_SATURATION,
    544             "None",
    545             "Low saturation",
    546             "High saturation"
    547         );
    548     }
    549 
    550     @Nullable
    551     public String getContrastDescription()
    552     {
    553         return getIndexedDescription(TAG_CONTRAST,
    554             "None",
    555             "Soft",
    556             "Hard"
    557         );
    558     }
    559 
    560     @Nullable
    561     public String getGainControlDescription()
    562     {
    563         return getIndexedDescription(TAG_GAIN_CONTROL,
    564             "None",
    565             "Low gain up",
    566             "Low gain down",
    567             "High gain up",
    568             "High gain down"
    569         );
    570     }
    571 
    572     @Nullable
    573     public String getSceneCaptureTypeDescription()
    574     {
    575         return getIndexedDescription(TAG_SCENE_CAPTURE_TYPE,
    576             "Standard",
    577             "Landscape",
    578             "Portrait",
    579             "Night scene"
    580         );
    581     }
    582 
    583     @Nullable
    584     public String get35mmFilmEquivFocalLengthDescription()
    585     {
    586         Integer value = _directory.getInteger(TAG_35MM_FILM_EQUIV_FOCAL_LENGTH);
    587         return value == null
    588             ? null
    589             : value == 0
    590                 ? "Unknown"
    591                 : getFocalLengthDescription(value);
    592     }
    593 
    594     @Nullable
    595     public String getDigitalZoomRatioDescription()
    596     {
    597         Rational value = _directory.getRational(TAG_DIGITAL_ZOOM_RATIO);
    598         return value == null
    599             ? null
    600             : value.getNumerator() == 0
    601                 ? "Digital zoom not used"
    602                 : new DecimalFormat("0.#").format(value.doubleValue());
    603     }
    604 
    605     @Nullable
    606     public String getWhiteBalanceModeDescription()
    607     {
    608         return getIndexedDescription(TAG_WHITE_BALANCE_MODE,
    609             "Auto white balance",
    610             "Manual white balance"
    611         );
    612     }
    613 
    614     @Nullable
    615     public String getExposureModeDescription()
    616     {
    617         return getIndexedDescription(TAG_EXPOSURE_MODE,
    618             "Auto exposure",
    619             "Manual exposure",
    620             "Auto bracket"
    621         );
    622     }
    623 
    624     @Nullable
    625     public String getCustomRenderedDescription()
    626     {
    627         return getIndexedDescription(TAG_CUSTOM_RENDERED,
    628             "Normal process",
    629             "Custom process"
    630         );
    631     }
    632 
    633     @Nullable
    634     public String getUserCommentDescription()
    635     {
    636         byte[] commentBytes = _directory.getByteArray(TAG_USER_COMMENT);
    637         if (commentBytes == null)
    638             return null;
    639         if (commentBytes.length == 0)
    640             return "";
    641 
    642         final Map<String, String> encodingMap = new HashMap<String, String>();
    643         encodingMap.put("ASCII", System.getProperty("file.encoding")); // Someone suggested "ISO-8859-1".
    644         encodingMap.put("UNICODE", "UTF-16LE");
    645         encodingMap.put("JIS", "Shift-JIS"); // We assume this charset for now.  Another suggestion is "JIS".
    646 
    647         try {
    648             if (commentBytes.length >= 10) {
    649                 String firstTenBytesString = new String(commentBytes, 0, 10);
    650 
    651                 // try each encoding name
    652                 for (Map.Entry<String, String> pair : encodingMap.entrySet()) {
    653                     String encodingName = pair.getKey();
    654                     String charset = pair.getValue();
    655                     if (firstTenBytesString.startsWith(encodingName)) {
    656                         // skip any null or blank characters commonly present after the encoding name, up to a limit of 10 from the start
    657                         for (int j = encodingName.length(); j < 10; j++) {
    658                             byte b = commentBytes[j];
    659                             if (b != '\0' && b != ' ')
    660                                 return new String(commentBytes, j, commentBytes.length - j, charset).trim();
    661                         }
    662                         return new String(commentBytes, 10, commentBytes.length - 10, charset).trim();
    663                     }
    664                 }
    665             }
    666             // special handling fell through, return a plain string representation
    667             return new String(commentBytes, System.getProperty("file.encoding")).trim();
    668         } catch (UnsupportedEncodingException ex) {
    669             return null;
    670         }
    671     }
    672 
    673     @Nullable
    674     public String getIsoEquivalentDescription()
    675     {
    676         // Have seen an exception here from files produced by ACDSEE that stored an int[] here with two values
    677         Integer isoEquiv = _directory.getInteger(TAG_ISO_EQUIVALENT);
    678         // There used to be a check here that multiplied ISO values < 50 by 200.
    679         // Issue 36 shows a smart-phone image from a Samsung Galaxy S2 with ISO-40.
    680         return isoEquiv != null
    681             ? Integer.toString(isoEquiv)
    682             : null;
    683     }
    684 
    685     @Nullable
    686     public String getExifVersionDescription()
    687     {
    688         return getVersionBytesDescription(TAG_EXIF_VERSION, 2);
    689     }
    690 
    691     @Nullable
    692     public String getFlashPixVersionDescription()
    693     {
    694         return getVersionBytesDescription(TAG_FLASHPIX_VERSION, 2);
    695     }
    696 
    697     @Nullable
    698     public String getSceneTypeDescription()
    699     {
    700         return getIndexedDescription(TAG_SCENE_TYPE,
    701             1,
    702             "Directly photographed image"
    703         );
    704     }
    705 
    706     /// <summary>
    707     /// String description of CFA Pattern
    708     /// </summary>
    709     /// <remarks>
    710     /// Converted from Exiftool version 10.33 created by Phil Harvey
    711     /// http://www.sno.phy.queensu.ca/~phil/exiftool/
    712     /// lib\Image\ExifTool\Exif.pm
    713     ///
    714     /// Indicates the color filter array (CFA) geometric pattern of the image sensor when a one-chip color area sensor is used.
    715     /// It does not apply to all sensing methods.
    716     /// </remarks>
    717     @Nullable
    718     public String getCfaPatternDescription()
    719     {
    720         return formatCFAPattern(decodeCfaPattern(TAG_CFA_PATTERN));
    721     }
    722 
    723     /// <summary>
    724     /// String description of CFA Pattern
    725     /// </summary>
    726     /// <remarks>
    727     /// Indicates the color filter array (CFA) geometric pattern of the image sensor when a one-chip color area sensor is used.
    728     /// It does not apply to all sensing methods.
    729     ///
    730     /// ExifDirectoryBase.TAG_CFA_PATTERN_2 holds only the pixel pattern. ExifDirectoryBase.TAG_CFA_REPEAT_PATTERN_DIM is expected to exist and pass
    731     /// some conditional tests.
    732     /// </remarks>
    733     @Nullable
    734     public String getCfaPattern2Description()
    735     {
    736         byte[] values = _directory.getByteArray(TAG_CFA_PATTERN_2);
    737         if (values == null)
    738             return null;
    739 
    740         int[] repeatPattern = _directory.getIntArray(TAG_CFA_REPEAT_PATTERN_DIM);
    741         if (repeatPattern == null)
    742             return String.format("Repeat Pattern not found for CFAPattern (%s)", super.getDescription(TAG_CFA_PATTERN_2));
    743 
    744         if (repeatPattern.length == 2 && values.length == (repeatPattern[0] * repeatPattern[1]))
    745         {
    746             int[] intpattern = new int[2 + values.length];
    747             intpattern[0] = repeatPattern[0];
    748             intpattern[1] = repeatPattern[1];
    749 
    750             for (int i = 0; i < values.length; i++)
    751                 intpattern[i + 2] = values[i] & 0xFF;   // convert the values[i] byte to unsigned
    752 
    753             return formatCFAPattern(intpattern);
    754         }
    755 
    756         return String.format("Unknown Pattern (%s)", super.getDescription(TAG_CFA_PATTERN_2));
    757     }
    758 
    759     @Nullable
    760     private static String formatCFAPattern(@Nullable int[] pattern)
    761     {
    762         if (pattern == null)
    763             return null;
    764         if (pattern.length < 2)
    765             return "<truncated data>";
    766         if (pattern[0] == 0 && pattern[1] == 0)
    767             return "<zero pattern size>";
    768 
    769         int end = 2 + pattern[0] * pattern[1];
    770         if (end > pattern.length)
    771             return "<invalid pattern size>";
    772 
    773         String[] cfaColors = { "Red", "Green", "Blue", "Cyan", "Magenta", "Yellow", "White" };
    774 
    775         StringBuilder ret = new StringBuilder();
    776         ret.append("[");
    777         for (int pos = 2; pos < end; pos++)
    778         {
    779             if (pattern[pos] <= cfaColors.length - 1)
    780                 ret.append(cfaColors[pattern[pos]]);
    781             else
    782                 ret.append("Unknown");      // indicated pattern position is outside the array bounds
    783 
    784             if ((pos - 2) % pattern[1] == 0)
    785                 ret.append(",");
    786             else if(pos != end - 1)
    787                 ret.append("][");
    788         }
    789         ret.append("]");
    790 
    791         return ret.toString();
    792     }
    793 
    794     /// <summary>
    795     /// Decode raw CFAPattern value
    796     /// </summary>
    797     /// <remarks>
    798     /// Converted from Exiftool version 10.33 created by Phil Harvey
    799     /// http://www.sno.phy.queensu.ca/~phil/exiftool/
    800     /// lib\Image\ExifTool\Exif.pm
    801     ///
    802     /// The value consists of:
    803     /// - Two short, being the grid width and height of the repeated pattern.
    804     /// - Next, for every pixel in that pattern, an identification code.
    805     /// </remarks>
    806     @Nullable
    807     private int[] decodeCfaPattern(int tagType)
    808     {
    809         int[] ret;
    810 
    811         byte[] values = _directory.getByteArray(tagType);
    812         if (values == null)
    813             return null;
    814 
    815         if (values.length < 4)
    816         {
    817             ret = new int[values.length];
    818             for (int i = 0; i < values.length; i++)
    819                 ret[i] = values[i];
    820             return ret;
    821         }
    822 
    823         ret = new int[values.length - 2];
    824 
    825         try {
    826             ByteArrayReader reader = new ByteArrayReader(values);
    827 
    828             // first two values should be read as 16-bits (2 bytes)
    829             short item0 = reader.getInt16(0);
    830             short item1 = reader.getInt16(2);
    831 
    832             Boolean copyArray = false;
    833             int end = 2 + item0 * item1;
    834             if (end > values.length) // sanity check in case of byte order problems; calculated 'end' should be <= length of the values
    835             {
    836                 // try swapping byte order (I have seen this order different than in EXIF)
    837                 reader.setMotorolaByteOrder(!reader.isMotorolaByteOrder());
    838                 item0 = reader.getInt16(0);
    839                 item1 = reader.getInt16(2);
    840 
    841                 if (values.length >= (2 + item0 * item1))
    842                     copyArray = true;
    843             }
    844             else
    845                 copyArray = true;
    846 
    847             if(copyArray)
    848             {
    849                 ret[0] = item0;
    850                 ret[1] = item1;
    851 
    852                 for (int i = 4; i < values.length; i++)
    853                     ret[i - 2] = reader.getInt8(i);
    854             }
    855         } catch (IOException ex) {
    856             _directory.addError("IO exception processing data: " + ex.getMessage());
    857         }
    858 
    859         return ret;
    860     }
    861 
    862     @Nullable
    863     public String getFileSourceDescription()
    864     {
    865         return getIndexedDescription(TAG_FILE_SOURCE,
    866             1,
    867             "Film Scanner",
    868             "Reflection Print Scanner",
    869             "Digital Still Camera (DSC)"
    870         );
    871     }
    872 
    873     @Nullable
    874     public String getExposureBiasDescription()
    875     {
    876         Rational value = _directory.getRational(TAG_EXPOSURE_BIAS);
    877         if (value == null)
    878             return null;
    879         return value.toSimpleString(true) + " EV";
    880     }
    881 
    882     @Nullable
    883     public String getMaxApertureValueDescription()
    884     {
    885         Double aperture = _directory.getDoubleObject(TAG_MAX_APERTURE);
    886         if (aperture == null)
    887             return null;
    888         double fStop = PhotographicConversions.apertureToFStop(aperture);
    889         return getFStopDescription(fStop);
    890     }
    891 
    892     @Nullable
    893     public String getApertureValueDescription()
    894     {
    895         Double aperture = _directory.getDoubleObject(TAG_APERTURE);
    896         if (aperture == null)
    897             return null;
    898         double fStop = PhotographicConversions.apertureToFStop(aperture);
    899         return getFStopDescription(fStop);
    900     }
    901 
    902     @Nullable
    903     public String getExposureProgramDescription()
    904     {
    905         return getIndexedDescription(TAG_EXPOSURE_PROGRAM,
    906             1,
    907             "Manual control",
    908             "Program normal",
    909             "Aperture priority",
    910             "Shutter priority",
    911             "Program creative (slow program)",
    912             "Program action (high-speed program)",
    913             "Portrait mode",
    914             "Landscape mode"
    915         );
    916     }
    917 
    918 
    919     @Nullable
    920     public String getFocalPlaneXResolutionDescription()
    921     {
    922         Rational rational = _directory.getRational(TAG_FOCAL_PLANE_X_RESOLUTION);
    923         if (rational == null)
    924             return null;
    925         final String unit = getFocalPlaneResolutionUnitDescription();
    926         return rational.getReciprocal().toSimpleString(_allowDecimalRepresentationOfRationals)
    927             + (unit == null ? "" : " " + unit.toLowerCase());
    928     }
    929 
    930     @Nullable
    931     public String getFocalPlaneYResolutionDescription()
    932     {
    933         Rational rational = _directory.getRational(TAG_FOCAL_PLANE_Y_RESOLUTION);
    934         if (rational == null)
    935             return null;
    936         final String unit = getFocalPlaneResolutionUnitDescription();
    937         return rational.getReciprocal().toSimpleString(_allowDecimalRepresentationOfRationals)
    938             + (unit == null ? "" : " " + unit.toLowerCase());
    939     }
    940 
    941     @Nullable
    942     public String getFocalPlaneResolutionUnitDescription()
    943     {
    944         // Unit of FocalPlaneXResolution/FocalPlaneYResolution.
    945         // '1' means no-unit, '2' inch, '3' centimeter.
    946         return getIndexedDescription(TAG_FOCAL_PLANE_RESOLUTION_UNIT,
    947             1,
    948             "(No unit)",
    949             "Inches",
    950             "cm"
    951         );
    952     }
    953 
    954     @Nullable
    955     public String getExifImageWidthDescription()
    956     {
    957         final Integer value = _directory.getInteger(TAG_EXIF_IMAGE_WIDTH);
     272    public String getImageWidthDescription()
     273    {
     274        String value = _directory.getString(TAG_IMAGE_WIDTH);
    958275        return value == null ? null : value + " pixels";
    959276    }
    960277
    961278    @Nullable
    962     public String getExifImageHeightDescription()
    963     {
    964         final Integer value = _directory.getInteger(TAG_EXIF_IMAGE_HEIGHT);
     279    public String getImageHeightDescription()
     280    {
     281        String value = _directory.getString(TAG_IMAGE_HEIGHT);
    965282        return value == null ? null : value + " pixels";
    966283    }
    967284
    968285    @Nullable
    969     public String getColorSpaceDescription()
    970     {
    971         final Integer value = _directory.getInteger(TAG_COLOR_SPACE);
    972         if (value == null)
    973             return null;
    974         if (value == 1)
    975             return "sRGB";
    976         if (value == 65535)
    977             return "Undefined";
    978         return "Unknown (" + value + ")";
    979     }
    980 
    981     @Nullable
    982     public String getFocalLengthDescription()
    983     {
    984         Rational value = _directory.getRational(TAG_FOCAL_LENGTH);
    985         return value == null ? null : getFocalLengthDescription(value.doubleValue());
    986     }
    987 
    988     @Nullable
    989     public String getFlashDescription()
    990     {
    991         /*
    992          * This is a bit mask.
    993          * 0 = flash fired
    994          * 1 = return detected
    995          * 2 = return able to be detected
    996          * 3 = unknown
    997          * 4 = auto used
    998          * 5 = unknown
    999          * 6 = red eye reduction used
    1000          */
    1001 
    1002         final Integer value = _directory.getInteger(TAG_FLASH);
    1003 
    1004         if (value == null)
    1005             return null;
    1006 
    1007         StringBuilder sb = new StringBuilder();
    1008 
    1009         if ((value & 0x1) != 0)
    1010             sb.append("Flash fired");
    1011         else
    1012             sb.append("Flash did not fire");
    1013 
    1014         // check if we're able to detect a return, before we mention it
    1015         if ((value & 0x4) != 0) {
    1016             if ((value & 0x2) != 0)
    1017                 sb.append(", return detected");
    1018             else
    1019                 sb.append(", return not detected");
    1020         }
    1021 
    1022         if ((value & 0x10) != 0)
    1023             sb.append(", auto");
    1024 
    1025         if ((value & 0x40) != 0)
    1026             sb.append(", red-eye reduction");
    1027 
    1028         return sb.toString();
    1029     }
    1030 
    1031     @Nullable
    1032     public String getWhiteBalanceDescription()
    1033     {
    1034         // See http://web.archive.org/web/20131018091152/http://exif.org/Exif2-2.PDF page 35
    1035         final Integer value = _directory.getInteger(TAG_WHITE_BALANCE);
    1036         if (value == null)
    1037             return null;
    1038         switch (value) {
    1039             case 0: return "Unknown";
    1040             case 1: return "Daylight";
    1041             case 2: return "Florescent";
    1042             case 3: return "Tungsten";
    1043             case 4: return "Flash";
    1044             case 9: return "Fine Weather";
    1045             case 10: return "Cloudy";
    1046             case 11: return "Shade";
    1047             case 12: return "Daylight Fluorescent";
    1048             case 13: return "Day White Fluorescent";
    1049             case 14: return "Cool White Fluorescent";
    1050             case 15: return "White Fluorescent";
    1051             case 16: return "Warm White Fluorescent";
    1052             case 17: return "Standard light";
    1053             case 18: return "Standard light (B)";
    1054             case 19: return "Standard light (C)";
    1055             case 20: return "D55";
    1056             case 21: return "D65";
    1057             case 22: return "D75";
    1058             case 23: return "D50";
    1059             case 24: return "Studio Tungsten";
    1060             case 255: return "(Other)";
    1061             default:
    1062                 return "Unknown (" + value + ")";
    1063         }
    1064     }
    1065 
    1066     @Nullable
    1067     public String getMeteringModeDescription()
    1068     {
    1069         // '0' means unknown, '1' average, '2' center weighted average, '3' spot
    1070         // '4' multi-spot, '5' multi-segment, '6' partial, '255' other
    1071         Integer value = _directory.getInteger(TAG_METERING_MODE);
    1072         if (value == null)
    1073             return null;
    1074         switch (value) {
    1075             case 0: return "Unknown";
    1076             case 1: return "Average";
    1077             case 2: return "Center weighted average";
    1078             case 3: return "Spot";
    1079             case 4: return "Multi-spot";
    1080             case 5: return "Multi-segment";
    1081             case 6: return "Partial";
    1082             case 255: return "(Other)";
    1083             default:
    1084                 return "Unknown (" + value + ")";
    1085         }
     286    public String getBitsPerSampleDescription()
     287    {
     288        String value = _directory.getString(TAG_BITS_PER_SAMPLE);
     289        return value == null ? null : value + " bits/component/pixel";
    1086290    }
    1087291
     
    1140344
    1141345    @Nullable
    1142     public String getSubjectDistanceDescription()
    1143     {
    1144         Rational value = _directory.getRational(TAG_SUBJECT_DISTANCE);
    1145         if (value == null)
    1146             return null;
    1147         DecimalFormat formatter = new DecimalFormat("0.0##");
    1148         return formatter.format(value.doubleValue()) + " metres";
     346    public String getPhotometricInterpretationDescription()
     347    {
     348        // Shows the color space of the image data components
     349        Integer value = _directory.getInteger(TAG_PHOTOMETRIC_INTERPRETATION);
     350        if (value == null)
     351            return null;
     352        switch (value) {
     353            case 0: return "WhiteIsZero";
     354            case 1: return "BlackIsZero";
     355            case 2: return "RGB";
     356            case 3: return "RGB Palette";
     357            case 4: return "Transparency Mask";
     358            case 5: return "CMYK";
     359            case 6: return "YCbCr";
     360            case 8: return "CIELab";
     361            case 9: return "ICCLab";
     362            case 10: return "ITULab";
     363            case 32803: return "Color Filter Array";
     364            case 32844: return "Pixar LogL";
     365            case 32845: return "Pixar LogLuv";
     366            case 32892: return "Linear Raw";
     367            default:
     368                return "Unknown colour space";
     369        }
     370    }
     371
     372    @Nullable
     373    public String getThresholdingDescription()
     374    {
     375        return getIndexedDescription(TAG_THRESHOLDING, 1,
     376            "No dithering or halftoning",
     377            "Ordered dither or halftone",
     378            "Randomized dither"
     379        );
     380    }
     381
     382    @Nullable
     383    public String getFillOrderDescription()
     384    {
     385        return getIndexedDescription(TAG_FILL_ORDER, 1,
     386            "Normal",
     387            "Reversed"
     388        );
     389    }
     390
     391    @Nullable
     392    public String getOrientationDescription()
     393    {
     394        return super.getOrientationDescription(TAG_ORIENTATION);
     395    }
     396
     397    @Nullable
     398    public String getSamplesPerPixelDescription()
     399    {
     400        String value = _directory.getString(TAG_SAMPLES_PER_PIXEL);
     401        return value == null ? null : value + " samples/pixel";
     402    }
     403
     404    @Nullable
     405    public String getRowsPerStripDescription()
     406    {
     407        final String value = _directory.getString(TAG_ROWS_PER_STRIP);
     408        return value == null ? null : value + " rows/strip";
     409    }
     410
     411    @Nullable
     412    public String getStripByteCountsDescription()
     413    {
     414        final String value = _directory.getString(TAG_STRIP_BYTE_COUNTS);
     415        return value == null ? null : value + " bytes";
     416    }
     417
     418    @Nullable
     419    public String getXResolutionDescription()
     420    {
     421        Rational value = _directory.getRational(TAG_X_RESOLUTION);
     422        if (value == null)
     423            return null;
     424        final String unit = getResolutionDescription();
     425        return String.format("%s dots per %s",
     426            value.toSimpleString(_allowDecimalRepresentationOfRationals),
     427            unit == null ? "unit" : unit.toLowerCase());
     428    }
     429
     430    @Nullable
     431    public String getYResolutionDescription()
     432    {
     433        Rational value = _directory.getRational(TAG_Y_RESOLUTION);
     434        if (value==null)
     435            return null;
     436        final String unit = getResolutionDescription();
     437        return String.format("%s dots per %s",
     438            value.toSimpleString(_allowDecimalRepresentationOfRationals),
     439            unit == null ? "unit" : unit.toLowerCase());
     440    }
     441
     442    @Nullable
     443    public String getPlanarConfigurationDescription()
     444    {
     445        // When image format is no compression YCbCr, this value shows byte aligns of YCbCr
     446        // data. If value is '1', Y/Cb/Cr value is chunky format, contiguous for each subsampling
     447        // pixel. If value is '2', Y/Cb/Cr value is separated and stored to Y plane/Cb plane/Cr
     448        // plane format.
     449        return getIndexedDescription(TAG_PLANAR_CONFIGURATION,
     450            1,
     451            "Chunky (contiguous for each subsampling pixel)",
     452            "Separate (Y-plane/Cb-plane/Cr-plane format)"
     453        );
     454    }
     455
     456    @Nullable
     457    public String getResolutionDescription()
     458    {
     459        // '1' means no-unit, '2' means inch, '3' means centimeter. Default value is '2'(inch)
     460        return getIndexedDescription(TAG_RESOLUTION_UNIT, 1, "(No unit)", "Inch", "cm");
     461    }
     462
     463    @Nullable
     464    public String getJpegProcDescription()
     465    {
     466        Integer value = _directory.getInteger(TAG_JPEG_PROC);
     467        if (value == null)
     468            return null;
     469        switch (value) {
     470            case 1: return "Baseline";
     471            case 14: return "Lossless";
     472            default:
     473                return "Unknown (" + value + ")";
     474        }
     475    }
     476
     477    @Nullable
     478    public String getYCbCrSubsamplingDescription()
     479    {
     480        int[] positions = _directory.getIntArray(TAG_YCBCR_SUBSAMPLING);
     481        if (positions == null || positions.length < 2)
     482            return null;
     483        if (positions[0] == 2 && positions[1] == 1) {
     484            return "YCbCr4:2:2";
     485        } else if (positions[0] == 2 && positions[1] == 2) {
     486            return "YCbCr4:2:0";
     487        } else {
     488            return "(Unknown)";
     489        }
     490    }
     491
     492    @Nullable
     493    public String getYCbCrPositioningDescription()
     494    {
     495        return getIndexedDescription(TAG_YCBCR_POSITIONING, 1, "Center of pixel array", "Datum point");
     496    }
     497
     498    @Nullable
     499    public String getReferenceBlackWhiteDescription()
     500    {
     501        // For some reason, sometimes this is read as a long[] and
     502        // getIntArray isn't able to deal with it
     503        int[] ints = _directory.getIntArray(TAG_REFERENCE_BLACK_WHITE);
     504        if (ints==null || ints.length < 6)
     505        {
     506            Object o = _directory.getObject(TAG_REFERENCE_BLACK_WHITE);
     507            if (o != null && (o instanceof long[]))
     508            {
     509                long[] longs = (long[])o;
     510                if (longs.length < 6)
     511                    return null;
     512
     513                ints = new int[longs.length];
     514                for (int i = 0; i < longs.length; i++)
     515                    ints[i] = (int)longs[i];
     516            }
     517            else
     518                return null;
     519        }
     520
     521        int blackR = ints[0];
     522        int whiteR = ints[1];
     523        int blackG = ints[2];
     524        int whiteG = ints[3];
     525        int blackB = ints[4];
     526        int whiteB = ints[5];
     527        return String.format("[%d,%d,%d] [%d,%d,%d]", blackR, blackG, blackB, whiteR, whiteG, whiteB);
     528    }
     529
     530    /// <summary>
     531    /// String description of CFA Pattern
     532    /// </summary>
     533    /// <remarks>
     534    /// Indicates the color filter array (CFA) geometric pattern of the image sensor when a one-chip color area sensor is used.
     535    /// It does not apply to all sensing methods.
     536    ///
     537    /// ExifDirectoryBase.TAG_CFA_PATTERN_2 holds only the pixel pattern. ExifDirectoryBase.TAG_CFA_REPEAT_PATTERN_DIM is expected to exist and pass
     538    /// some conditional tests.
     539    /// </remarks>
     540    @Nullable
     541    public String getCfaPattern2Description()
     542    {
     543        byte[] values = _directory.getByteArray(TAG_CFA_PATTERN_2);
     544        if (values == null)
     545            return null;
     546
     547        int[] repeatPattern = _directory.getIntArray(TAG_CFA_REPEAT_PATTERN_DIM);
     548        if (repeatPattern == null)
     549            return String.format("Repeat Pattern not found for CFAPattern (%s)", super.getDescription(TAG_CFA_PATTERN_2));
     550
     551        if (repeatPattern.length == 2 && values.length == (repeatPattern[0] * repeatPattern[1]))
     552        {
     553            int[] intpattern = new int[2 + values.length];
     554            intpattern[0] = repeatPattern[0];
     555            intpattern[1] = repeatPattern[1];
     556
     557            for (int i = 0; i < values.length; i++)
     558                intpattern[i + 2] = values[i] & 0xFF;   // convert the values[i] byte to unsigned
     559
     560            return formatCFAPattern(intpattern);
     561        }
     562
     563        return String.format("Unknown Pattern (%s)", super.getDescription(TAG_CFA_PATTERN_2));
     564    }
     565
     566    @Nullable
     567    private static String formatCFAPattern(@Nullable int[] pattern)
     568    {
     569        if (pattern == null)
     570            return null;
     571        if (pattern.length < 2)
     572            return "<truncated data>";
     573        if (pattern[0] == 0 && pattern[1] == 0)
     574            return "<zero pattern size>";
     575
     576        int end = 2 + pattern[0] * pattern[1];
     577        if (end > pattern.length)
     578            return "<invalid pattern size>";
     579
     580        String[] cfaColors = { "Red", "Green", "Blue", "Cyan", "Magenta", "Yellow", "White" };
     581
     582        StringBuilder ret = new StringBuilder();
     583        ret.append("[");
     584        for (int pos = 2; pos < end; pos++)
     585        {
     586            if (pattern[pos] <= cfaColors.length - 1)
     587                ret.append(cfaColors[pattern[pos]]);
     588            else
     589                ret.append("Unknown");      // indicated pattern position is outside the array bounds
     590
     591            if ((pos - 2) % pattern[1] == 0)
     592                ret.append(",");
     593            else if(pos != end - 1)
     594                ret.append("][");
     595        }
     596        ret.append("]");
     597
     598        return ret.toString();
     599    }
     600
     601    @Nullable
     602    public String getExposureTimeDescription()
     603    {
     604        String value = _directory.getString(TAG_EXPOSURE_TIME);
     605        return value == null ? null : value + " sec";
     606    }
     607
     608    @Nullable
     609    public String getFNumberDescription()
     610    {
     611        Rational value = _directory.getRational(TAG_FNUMBER);
     612        if (value == null)
     613            return null;
     614        return getFStopDescription(value.doubleValue());
     615    }
     616
     617    @Nullable
     618    public String getExposureProgramDescription()
     619    {
     620        return getIndexedDescription(TAG_EXPOSURE_PROGRAM,
     621            1,
     622            "Manual control",
     623            "Program normal",
     624            "Aperture priority",
     625            "Shutter priority",
     626            "Program creative (slow program)",
     627            "Program action (high-speed program)",
     628            "Portrait mode",
     629            "Landscape mode"
     630        );
     631    }
     632
     633    @Nullable
     634    public String getIsoEquivalentDescription()
     635    {
     636        // Have seen an exception here from files produced by ACDSEE that stored an int[] here with two values
     637        Integer isoEquiv = _directory.getInteger(TAG_ISO_EQUIVALENT);
     638        // There used to be a check here that multiplied ISO values < 50 by 200.
     639        // Issue 36 shows a smart-phone image from a Samsung Galaxy S2 with ISO-40.
     640        return isoEquiv != null
     641            ? Integer.toString(isoEquiv)
     642            : null;
     643    }
     644
     645    @Nullable
     646    public String getSensitivityTypeRangeDescription()
     647    {
     648        return getIndexedDescription(TAG_SENSITIVITY_TYPE,
     649            "Unknown",
     650            "Standard Output Sensitivity",
     651            "Recommended Exposure Index",
     652            "ISO Speed",
     653            "Standard Output Sensitivity and Recommended Exposure Index",
     654            "Standard Output Sensitivity and ISO Speed",
     655            "Recommended Exposure Index and ISO Speed",
     656            "Standard Output Sensitivity, Recommended Exposure Index and ISO Speed"
     657        );
     658    }
     659
     660    @Nullable
     661    public String getExifVersionDescription()
     662    {
     663        return getVersionBytesDescription(TAG_EXIF_VERSION, 2);
     664    }
     665
     666    @Nullable
     667    public String getComponentConfigurationDescription()
     668    {
     669        int[] components = _directory.getIntArray(TAG_COMPONENTS_CONFIGURATION);
     670        if (components == null)
     671            return null;
     672        String[] componentStrings = {"", "Y", "Cb", "Cr", "R", "G", "B"};
     673        StringBuilder componentConfig = new StringBuilder();
     674        for (int i = 0; i < Math.min(4, components.length); i++) {
     675            int j = components[i];
     676            if (j > 0 && j < componentStrings.length) {
     677                componentConfig.append(componentStrings[j]);
     678            }
     679        }
     680        return componentConfig.toString();
    1149681    }
    1150682
     
    1162694
    1163695    @Nullable
    1164     public String getExposureTimeDescription()
    1165     {
    1166         String value = _directory.getString(TAG_EXPOSURE_TIME);
    1167         return value == null ? null : value + " sec";
    1168     }
    1169 
    1170     @Nullable
    1171696    public String getShutterSpeedDescription()
    1172697    {
     
    1175700
    1176701    @Nullable
    1177     public String getFNumberDescription()
    1178     {
    1179         Rational value = _directory.getRational(TAG_FNUMBER);
    1180         if (value == null)
    1181             return null;
    1182         return getFStopDescription(value.doubleValue());
     702    public String getApertureValueDescription()
     703    {
     704        Double aperture = _directory.getDoubleObject(TAG_APERTURE);
     705        if (aperture == null)
     706            return null;
     707        double fStop = PhotographicConversions.apertureToFStop(aperture);
     708        return getFStopDescription(fStop);
     709    }
     710
     711    @Nullable
     712    public String getBrightnessValueDescription()
     713    {
     714        Rational value = _directory.getRational(TAG_BRIGHTNESS_VALUE);
     715        if (value == null)
     716            return null;
     717        if (value.getNumerator() == 0xFFFFFFFFL)
     718            return "Unknown";
     719        DecimalFormat formatter = new DecimalFormat("0.0##");
     720        return formatter.format(value.doubleValue());
     721    }
     722
     723    @Nullable
     724    public String getExposureBiasDescription()
     725    {
     726        Rational value = _directory.getRational(TAG_EXPOSURE_BIAS);
     727        if (value == null)
     728            return null;
     729        return value.toSimpleString(true) + " EV";
     730    }
     731
     732    @Nullable
     733    public String getMaxApertureValueDescription()
     734    {
     735        Double aperture = _directory.getDoubleObject(TAG_MAX_APERTURE);
     736        if (aperture == null)
     737            return null;
     738        double fStop = PhotographicConversions.apertureToFStop(aperture);
     739        return getFStopDescription(fStop);
     740    }
     741
     742    @Nullable
     743    public String getSubjectDistanceDescription()
     744    {
     745        Rational value = _directory.getRational(TAG_SUBJECT_DISTANCE);
     746        if (value == null)
     747            return null;
     748        if (value.getNumerator() == 0xFFFFFFFFL)
     749            return "Infinity";
     750        if (value.getNumerator() == 0)
     751            return "Unknown";
     752        DecimalFormat formatter = new DecimalFormat("0.0##");
     753        return formatter.format(value.doubleValue()) + " metres";
     754    }
     755
     756    @Nullable
     757    public String getMeteringModeDescription()
     758    {
     759        // '0' means unknown, '1' average, '2' center weighted average, '3' spot
     760        // '4' multi-spot, '5' multi-segment, '6' partial, '255' other
     761        Integer value = _directory.getInteger(TAG_METERING_MODE);
     762        if (value == null)
     763            return null;
     764        switch (value) {
     765            case 0: return "Unknown";
     766            case 1: return "Average";
     767            case 2: return "Center weighted average";
     768            case 3: return "Spot";
     769            case 4: return "Multi-spot";
     770            case 5: return "Multi-segment";
     771            case 6: return "Partial";
     772            case 255: return "(Other)";
     773            default:
     774                return "Unknown (" + value + ")";
     775        }
     776    }
     777
     778    @Nullable
     779    public String getWhiteBalanceDescription()
     780    {
     781        // See http://web.archive.org/web/20131018091152/http://exif.org/Exif2-2.PDF page 35
     782        final Integer value = _directory.getInteger(TAG_WHITE_BALANCE);
     783        if (value == null)
     784            return null;
     785        switch (value) {
     786            case 0: return "Unknown";
     787            case 1: return "Daylight";
     788            case 2: return "Florescent";
     789            case 3: return "Tungsten";
     790            case 4: return "Flash";
     791            case 9: return "Fine Weather";
     792            case 10: return "Cloudy";
     793            case 11: return "Shade";
     794            case 12: return "Daylight Fluorescent";
     795            case 13: return "Day White Fluorescent";
     796            case 14: return "Cool White Fluorescent";
     797            case 15: return "White Fluorescent";
     798            case 16: return "Warm White Fluorescent";
     799            case 17: return "Standard light";
     800            case 18: return "Standard light (B)";
     801            case 19: return "Standard light (C)";
     802            case 20: return "D55";
     803            case 21: return "D65";
     804            case 22: return "D75";
     805            case 23: return "D50";
     806            case 24: return "Studio Tungsten";
     807            case 255: return "(Other)";
     808            default:
     809                return "Unknown (" + value + ")";
     810        }
     811    }
     812
     813    @Nullable
     814    public String getFlashDescription()
     815    {
     816        /*
     817         * This is a bit mask.
     818         * 0 = flash fired
     819         * 1 = return detected
     820         * 2 = return able to be detected
     821         * 3 = unknown
     822         * 4 = auto used
     823         * 5 = unknown
     824         * 6 = red eye reduction used
     825         */
     826
     827        final Integer value = _directory.getInteger(TAG_FLASH);
     828
     829        if (value == null)
     830            return null;
     831
     832        StringBuilder sb = new StringBuilder();
     833
     834        if ((value & 0x1) != 0)
     835            sb.append("Flash fired");
     836        else
     837            sb.append("Flash did not fire");
     838
     839        // check if we're able to detect a return, before we mention it
     840        if ((value & 0x4) != 0) {
     841            if ((value & 0x2) != 0)
     842                sb.append(", return detected");
     843            else
     844                sb.append(", return not detected");
     845        }
     846
     847        // If 0x10 is set and the lowest byte is not zero - then flash is Auto
     848        if ((value & 0x10) != 0 && (value & 0x0F) != 0)
     849            sb.append(", auto");
     850
     851        if ((value & 0x40) != 0)
     852            sb.append(", red-eye reduction");
     853
     854        return sb.toString();
     855    }
     856
     857    @Nullable
     858    public String getFocalLengthDescription()
     859    {
     860        Rational value = _directory.getRational(TAG_FOCAL_LENGTH);
     861        return value == null ? null : getFocalLengthDescription(value.doubleValue());
     862    }
     863
     864    @Nullable
     865    public String getUserCommentDescription()
     866    {
     867        return getEncodedTextDescription(TAG_USER_COMMENT);
     868    }
     869
     870    @Nullable
     871    public String getTemperatureDescription()
     872    {
     873        Rational value = _directory.getRational(TAG_TEMPERATURE);
     874        if (value == null)
     875            return null;
     876        if (value.getDenominator() == 0xFFFFFFFFL)
     877            return "Unknown";
     878        DecimalFormat formatter = new DecimalFormat("0.0");
     879        return formatter.format(value.doubleValue()) + " °C";
     880    }
     881
     882    @Nullable
     883    public String getHumidityDescription()
     884    {
     885        Rational value = _directory.getRational(TAG_HUMIDITY);
     886        if (value == null)
     887            return null;
     888        if (value.getDenominator() == 0xFFFFFFFFL)
     889            return "Unknown";
     890        DecimalFormat formatter = new DecimalFormat("0.0");
     891        return formatter.format(value.doubleValue()) + " %";
     892    }
     893
     894    @Nullable
     895    public String getPressureDescription()
     896    {
     897        Rational value = _directory.getRational(TAG_PRESSURE);
     898        if (value == null)
     899            return null;
     900        if (value.getDenominator() == 0xFFFFFFFFL)
     901            return "Unknown";
     902        DecimalFormat formatter = new DecimalFormat("0.0");
     903        return formatter.format(value.doubleValue()) + " hPa";
     904    }
     905
     906    @Nullable
     907    public String getWaterDepthDescription()
     908    {
     909        Rational value = _directory.getRational(TAG_WATER_DEPTH);
     910        if (value == null)
     911            return null;
     912        if (value.getDenominator() == 0xFFFFFFFFL)
     913            return "Unknown";
     914        DecimalFormat formatter = new DecimalFormat("0.0##");
     915        return formatter.format(value.doubleValue()) + " metres";
     916    }
     917
     918    @Nullable
     919    public String getAccelerationDescription()
     920    {
     921        Rational value = _directory.getRational(TAG_ACCELERATION);
     922        if (value == null)
     923            return null;
     924        if (value.getDenominator() == 0xFFFFFFFFL)
     925            return "Unknown";
     926        DecimalFormat formatter = new DecimalFormat("0.0##");
     927        return formatter.format(value.doubleValue()) + " mGal";
     928    }
     929
     930    @Nullable
     931    public String getCameraElevationAngleDescription()
     932    {
     933        Rational value = _directory.getRational(TAG_CAMERA_ELEVATION_ANGLE);
     934        if (value == null)
     935            return null;
     936        if (value.getDenominator() == 0xFFFFFFFFL)
     937            return "Unknown";
     938        DecimalFormat formatter = new DecimalFormat("0.##");
     939        return formatter.format(value.doubleValue()) + " degrees";
     940    }
     941
     942    /** The Windows specific tags uses plain Unicode. */
     943    @Nullable
     944    private String getUnicodeDescription(int tag)
     945    {
     946        byte[] bytes = _directory.getByteArray(tag);
     947        if (bytes == null)
     948            return null;
     949        try {
     950            // Decode the unicode string and trim the unicode zero "\0" from the end.
     951            return new String(bytes, "UTF-16LE").trim();
     952        } catch (UnsupportedEncodingException ex) {
     953            return null;
     954        }
     955    }
     956
     957    @Nullable
     958    public String getWindowsTitleDescription()
     959    {
     960        return getUnicodeDescription(TAG_WIN_TITLE);
     961    }
     962
     963    @Nullable
     964    public String getWindowsCommentDescription()
     965    {
     966        return getUnicodeDescription(TAG_WIN_COMMENT);
     967    }
     968
     969    @Nullable
     970    public String getWindowsAuthorDescription()
     971    {
     972        return getUnicodeDescription(TAG_WIN_AUTHOR);
     973    }
     974
     975    @Nullable
     976    public String getWindowsKeywordsDescription()
     977    {
     978        return getUnicodeDescription(TAG_WIN_KEYWORDS);
     979    }
     980
     981    @Nullable
     982    public String getWindowsSubjectDescription()
     983    {
     984        return getUnicodeDescription(TAG_WIN_SUBJECT);
     985    }
     986
     987    @Nullable
     988    public String getFlashPixVersionDescription()
     989    {
     990        return getVersionBytesDescription(TAG_FLASHPIX_VERSION, 2);
     991    }
     992
     993    @Nullable
     994    public String getColorSpaceDescription()
     995    {
     996        final Integer value = _directory.getInteger(TAG_COLOR_SPACE);
     997        if (value == null)
     998            return null;
     999        if (value == 1)
     1000            return "sRGB";
     1001        if (value == 65535)
     1002            return "Undefined";
     1003        return "Unknown (" + value + ")";
     1004    }
     1005
     1006    @Nullable
     1007    public String getExifImageWidthDescription()
     1008    {
     1009        final Integer value = _directory.getInteger(TAG_EXIF_IMAGE_WIDTH);
     1010        return value == null ? null : value + " pixels";
     1011    }
     1012
     1013    @Nullable
     1014    public String getExifImageHeightDescription()
     1015    {
     1016        final Integer value = _directory.getInteger(TAG_EXIF_IMAGE_HEIGHT);
     1017        return value == null ? null : value + " pixels";
     1018    }
     1019
     1020    @Nullable
     1021    public String getFocalPlaneXResolutionDescription()
     1022    {
     1023        Rational rational = _directory.getRational(TAG_FOCAL_PLANE_X_RESOLUTION);
     1024        if (rational == null)
     1025            return null;
     1026        final String unit = getFocalPlaneResolutionUnitDescription();
     1027        return rational.getReciprocal().toSimpleString(_allowDecimalRepresentationOfRationals)
     1028            + (unit == null ? "" : " " + unit.toLowerCase());
     1029    }
     1030
     1031    @Nullable
     1032    public String getFocalPlaneYResolutionDescription()
     1033    {
     1034        Rational rational = _directory.getRational(TAG_FOCAL_PLANE_Y_RESOLUTION);
     1035        if (rational == null)
     1036            return null;
     1037        final String unit = getFocalPlaneResolutionUnitDescription();
     1038        return rational.getReciprocal().toSimpleString(_allowDecimalRepresentationOfRationals)
     1039            + (unit == null ? "" : " " + unit.toLowerCase());
     1040    }
     1041
     1042    @Nullable
     1043    public String getFocalPlaneResolutionUnitDescription()
     1044    {
     1045        // Unit of FocalPlaneXResolution/FocalPlaneYResolution.
     1046        // '1' means no-unit, '2' inch, '3' centimeter.
     1047        return getIndexedDescription(TAG_FOCAL_PLANE_RESOLUTION_UNIT,
     1048            1,
     1049            "(No unit)",
     1050            "Inches",
     1051            "cm"
     1052        );
    11831053    }
    11841054
     
    12031073
    12041074    @Nullable
    1205     public String getComponentConfigurationDescription()
    1206     {
    1207         int[] components = _directory.getIntArray(TAG_COMPONENTS_CONFIGURATION);
    1208         if (components == null)
    1209             return null;
    1210         String[] componentStrings = {"", "Y", "Cb", "Cr", "R", "G", "B"};
    1211         StringBuilder componentConfig = new StringBuilder();
    1212         for (int i = 0; i < Math.min(4, components.length); i++) {
    1213             int j = components[i];
    1214             if (j > 0 && j < componentStrings.length) {
    1215                 componentConfig.append(componentStrings[j]);
     1075    public String getFileSourceDescription()
     1076    {
     1077        return getIndexedDescription(TAG_FILE_SOURCE,
     1078            1,
     1079            "Film Scanner",
     1080            "Reflection Print Scanner",
     1081            "Digital Still Camera (DSC)"
     1082        );
     1083    }
     1084
     1085    @Nullable
     1086    public String getSceneTypeDescription()
     1087    {
     1088        return getIndexedDescription(TAG_SCENE_TYPE,
     1089            1,
     1090            "Directly photographed image"
     1091        );
     1092    }
     1093
     1094    /// <summary>
     1095    /// String description of CFA Pattern
     1096    /// </summary>
     1097    /// <remarks>
     1098    /// Converted from Exiftool version 10.33 created by Phil Harvey
     1099    /// http://www.sno.phy.queensu.ca/~phil/exiftool/
     1100    /// lib\Image\ExifTool\Exif.pm
     1101    ///
     1102    /// Indicates the color filter array (CFA) geometric pattern of the image sensor when a one-chip color area sensor is used.
     1103    /// It does not apply to all sensing methods.
     1104    /// </remarks>
     1105    @Nullable
     1106    public String getCfaPatternDescription()
     1107    {
     1108        return formatCFAPattern(decodeCfaPattern(TAG_CFA_PATTERN));
     1109    }
     1110
     1111    /// <summary>
     1112    /// Decode raw CFAPattern value
     1113    /// </summary>
     1114    /// <remarks>
     1115    /// Converted from Exiftool version 10.33 created by Phil Harvey
     1116    /// http://www.sno.phy.queensu.ca/~phil/exiftool/
     1117    /// lib\Image\ExifTool\Exif.pm
     1118    ///
     1119    /// The value consists of:
     1120    /// - Two short, being the grid width and height of the repeated pattern.
     1121    /// - Next, for every pixel in that pattern, an identification code.
     1122    /// </remarks>
     1123    @Nullable
     1124    private int[] decodeCfaPattern(int tagType)
     1125    {
     1126        int[] ret;
     1127
     1128        byte[] values = _directory.getByteArray(tagType);
     1129        if (values == null)
     1130            return null;
     1131
     1132        if (values.length < 4)
     1133        {
     1134            ret = new int[values.length];
     1135            for (int i = 0; i < values.length; i++)
     1136                ret[i] = values[i];
     1137            return ret;
     1138        }
     1139
     1140        ret = new int[values.length - 2];
     1141
     1142        try {
     1143            ByteArrayReader reader = new ByteArrayReader(values);
     1144
     1145            // first two values should be read as 16-bits (2 bytes)
     1146            short item0 = reader.getInt16(0);
     1147            short item1 = reader.getInt16(2);
     1148
     1149            Boolean copyArray = false;
     1150            int end = 2 + item0 * item1;
     1151            if (end > values.length) // sanity check in case of byte order problems; calculated 'end' should be <= length of the values
     1152            {
     1153                // try swapping byte order (I have seen this order different than in EXIF)
     1154                reader.setMotorolaByteOrder(!reader.isMotorolaByteOrder());
     1155                item0 = reader.getInt16(0);
     1156                item1 = reader.getInt16(2);
     1157
     1158                if (values.length >= (2 + item0 * item1))
     1159                    copyArray = true;
    12161160            }
    1217         }
    1218         return componentConfig.toString();
    1219     }
    1220 
    1221     @Nullable
    1222     public String getJpegProcDescription()
    1223     {
    1224         Integer value = _directory.getInteger(TAG_JPEG_PROC);
    1225         if (value == null)
    1226             return null;
    1227         switch (value) {
    1228             case 1: return "Baseline";
    1229             case 14: return "Lossless";
    1230             default:
    1231                 return "Unknown (" + value + ")";
    1232         }
     1161            else
     1162                copyArray = true;
     1163
     1164            if(copyArray)
     1165            {
     1166                ret[0] = item0;
     1167                ret[1] = item1;
     1168
     1169                for (int i = 4; i < values.length; i++)
     1170                    ret[i - 2] = reader.getInt8(i);
     1171            }
     1172        } catch (IOException ex) {
     1173            _directory.addError("IO exception processing data: " + ex.getMessage());
     1174        }
     1175
     1176        return ret;
     1177    }
     1178
     1179    @Nullable
     1180    public String getCustomRenderedDescription()
     1181    {
     1182        return getIndexedDescription(TAG_CUSTOM_RENDERED,
     1183            "Normal process",
     1184            "Custom process"
     1185        );
     1186    }
     1187
     1188    @Nullable
     1189    public String getExposureModeDescription()
     1190    {
     1191        return getIndexedDescription(TAG_EXPOSURE_MODE,
     1192            "Auto exposure",
     1193            "Manual exposure",
     1194            "Auto bracket"
     1195        );
     1196    }
     1197
     1198    @Nullable
     1199    public String getWhiteBalanceModeDescription()
     1200    {
     1201        return getIndexedDescription(TAG_WHITE_BALANCE_MODE,
     1202            "Auto white balance",
     1203            "Manual white balance"
     1204        );
     1205    }
     1206
     1207    @Nullable
     1208    public String getDigitalZoomRatioDescription()
     1209    {
     1210        Rational value = _directory.getRational(TAG_DIGITAL_ZOOM_RATIO);
     1211        return value == null
     1212            ? null
     1213            : value.getNumerator() == 0
     1214                ? "Digital zoom not used"
     1215                : new DecimalFormat("0.#").format(value.doubleValue());
     1216    }
     1217
     1218    @Nullable
     1219    public String get35mmFilmEquivFocalLengthDescription()
     1220    {
     1221        Integer value = _directory.getInteger(TAG_35MM_FILM_EQUIV_FOCAL_LENGTH);
     1222        return value == null
     1223            ? null
     1224            : value == 0
     1225                ? "Unknown"
     1226                : getFocalLengthDescription(value);
     1227    }
     1228
     1229    @Nullable
     1230    public String getSceneCaptureTypeDescription()
     1231    {
     1232        return getIndexedDescription(TAG_SCENE_CAPTURE_TYPE,
     1233            "Standard",
     1234            "Landscape",
     1235            "Portrait",
     1236            "Night scene"
     1237        );
     1238    }
     1239
     1240    @Nullable
     1241    public String getGainControlDescription()
     1242    {
     1243        return getIndexedDescription(TAG_GAIN_CONTROL,
     1244            "None",
     1245            "Low gain up",
     1246            "Low gain down",
     1247            "High gain up",
     1248            "High gain down"
     1249        );
     1250    }
     1251
     1252    @Nullable
     1253    public String getContrastDescription()
     1254    {
     1255        return getIndexedDescription(TAG_CONTRAST,
     1256            "None",
     1257            "Soft",
     1258            "Hard"
     1259        );
     1260    }
     1261
     1262    @Nullable
     1263    public String getSaturationDescription()
     1264    {
     1265        return getIndexedDescription(TAG_SATURATION,
     1266            "None",
     1267            "Low saturation",
     1268            "High saturation"
     1269        );
     1270    }
     1271
     1272    @Nullable
     1273    public String getSharpnessDescription()
     1274    {
     1275        return getIndexedDescription(TAG_SHARPNESS,
     1276            "None",
     1277            "Low",
     1278            "Hard"
     1279        );
     1280    }
     1281
     1282    @Nullable
     1283    public String getSubjectDistanceRangeDescription()
     1284    {
     1285        return getIndexedDescription(TAG_SUBJECT_DISTANCE_RANGE,
     1286            "Unknown",
     1287            "Macro",
     1288            "Close view",
     1289            "Distant view"
     1290        );
     1291    }
     1292
     1293    @Nullable
     1294    public String getLensSpecificationDescription()
     1295    {
     1296        return getLensSpecificationDescription(TAG_LENS_SPECIFICATION);
    12331297    }
    12341298}
  • trunk/src/com/drew/metadata/exif/ExifDirectoryBase.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    189189    public static final int TAG_FNUMBER                           = 0x829D;
    190190    public static final int TAG_IPTC_NAA                          = 0x83BB;
     191    public static final int TAG_PHOTOSHOP_SETTINGS                = 0x8649;
    191192    public static final int TAG_INTER_COLOR_PROFILE               = 0x8773;
    192193    /**
     
    230231    public static final int TAG_STANDARD_OUTPUT_SENSITIVITY       = 0x8831;
    231232    public static final int TAG_RECOMMENDED_EXPOSURE_INDEX        = 0x8832;
    232     /** Non-standard, but in use. */
    233     public static final int TAG_TIME_ZONE_OFFSET                  = 0x882A;
    234     public static final int TAG_SELF_TIMER_MODE                   = 0x882B;
     233    public static final int TAG_ISO_SPEED                         = 0x8833;
     234    public static final int TAG_ISO_SPEED_LATITUDE_YYY            = 0x8834;
     235    public static final int TAG_ISO_SPEED_LATITUDE_ZZZ            = 0x8835;
    235236
    236237    public static final int TAG_EXIF_VERSION                      = 0x9000;
    237238    public static final int TAG_DATETIME_ORIGINAL                 = 0x9003;
    238239    public static final int TAG_DATETIME_DIGITIZED                = 0x9004;
     240    public static final int TAG_OFFSET_TIME                       = 0x9010;
     241    public static final int TAG_OFFSET_TIME_ORIGINAL              = 0x9011;
     242    public static final int TAG_OFFSET_TIME_DIGITIZED             = 0x9012;
    239243
    240244    public static final int TAG_COMPONENTS_CONFIGURATION          = 0x9101;
     
    357361    public static final int TAG_SUBSECOND_TIME_ORIGINAL           = 0x9291;
    358362    public static final int TAG_SUBSECOND_TIME_DIGITIZED          = 0x9292;
     363
     364    public static final int TAG_TEMPERATURE                       = 0x9400;
     365    public static final int TAG_HUMIDITY                          = 0x9401;
     366    public static final int TAG_PRESSURE                          = 0x9402;
     367    public static final int TAG_WATER_DEPTH                       = 0x9403;
     368    public static final int TAG_ACCELERATION                      = 0x9404;
     369    public static final int TAG_CAMERA_ELEVATION_ANGLE            = 0x9405;
    359370
    360371    /** The image title, as used by Windows XP. */
     
    671682        map.put(TAG_FNUMBER, "F-Number");
    672683        map.put(TAG_IPTC_NAA, "IPTC/NAA");
     684        map.put(TAG_PHOTOSHOP_SETTINGS, "Photoshop Settings");
    673685        map.put(TAG_INTER_COLOR_PROFILE, "Inter Color Profile");
    674686        map.put(TAG_EXPOSURE_PROGRAM, "Exposure Program");
     
    682694        map.put(TAG_STANDARD_OUTPUT_SENSITIVITY, "Standard Output Sensitivity");
    683695        map.put(TAG_RECOMMENDED_EXPOSURE_INDEX, "Recommended Exposure Index");
    684         map.put(TAG_TIME_ZONE_OFFSET, "Time Zone Offset");
    685         map.put(TAG_SELF_TIMER_MODE, "Self Timer Mode");
     696        map.put(TAG_ISO_SPEED, "ISO Speed");
     697        map.put(TAG_ISO_SPEED_LATITUDE_YYY, "ISO Speed Latitude yyy");
     698        map.put(TAG_ISO_SPEED_LATITUDE_ZZZ, "ISO Speed Latitude zzz");
    686699        map.put(TAG_EXIF_VERSION, "Exif Version");
    687700        map.put(TAG_DATETIME_ORIGINAL, "Date/Time Original");
    688701        map.put(TAG_DATETIME_DIGITIZED, "Date/Time Digitized");
     702        map.put(TAG_OFFSET_TIME, "Offset Time");
     703        map.put(TAG_OFFSET_TIME_ORIGINAL, "Offset Time Original");
     704        map.put(TAG_OFFSET_TIME_DIGITIZED, "Offset Time Digitized");
    689705        map.put(TAG_COMPONENTS_CONFIGURATION, "Components Configuration");
    690706        map.put(TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL, "Compressed Bits Per Pixel");
     
    715731        map.put(TAG_SUBSECOND_TIME_ORIGINAL, "Sub-Sec Time Original");
    716732        map.put(TAG_SUBSECOND_TIME_DIGITIZED, "Sub-Sec Time Digitized");
     733        map.put(TAG_TEMPERATURE, "Temperature");
     734        map.put(TAG_HUMIDITY, "Humidity");
     735        map.put(TAG_PRESSURE, "Pressure");
     736        map.put(TAG_WATER_DEPTH, "Water Depth");
     737        map.put(TAG_ACCELERATION, "Acceleration");
     738        map.put(TAG_CAMERA_ELEVATION_ANGLE, "Camera Elevation Angle");
    717739        map.put(TAG_WIN_TITLE, "Windows XP Title");
    718740        map.put(TAG_WIN_COMMENT, "Windows XP Comment");
  • trunk/src/com/drew/metadata/exif/ExifIFD0Descriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/ExifIFD0Directory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2222package com.drew.metadata.exif;
    2323
     24import com.drew.lang.annotations.NotNull;
     25
    2426import java.util.HashMap;
    25 
    26 import com.drew.lang.annotations.NotNull;
    2727
    2828/**
  • trunk/src/com/drew/metadata/exif/ExifImageDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/ExifImageDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/ExifInteropDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/ExifInteropDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/ExifReader.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/ExifSubIFDDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/ExifSubIFDDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2323import com.drew.lang.annotations.NotNull;
    2424import com.drew.lang.annotations.Nullable;
     25import com.drew.metadata.Directory;
    2526
    2627import java.util.Date;
     
    6768
    6869    /**
    69      * Parses the date/time tag and the subsecond tag to obtain a single Date object with milliseconds
    70      * representing the date and time when this image was captured.  Attempts will be made to parse the
    71      * values as though it is in the GMT {@link TimeZone}.
     70     * Parses the date/time tag, the subsecond tag and the time offset tag to obtain a single Date
     71     * object with milliseconds representing the date and time when this image was modified.  If
     72     * the time offset tag does not exist, attempts will be made to parse the values as though it is
     73     * in the GMT {@link TimeZone}.
     74     *
     75     * @return A Date object representing when this image was modified, if possible, otherwise null
     76     */
     77    @Nullable
     78    public Date getDateModified()
     79    {
     80        return getDateModified(null);
     81    }
     82
     83    /**
     84     * Parses the date/time tag, the subsecond tag and the time offset tag to obtain a single Date
     85     * object with milliseconds representing the date and time when this image was modified.  If
     86     * the time offset tag does not exist, attempts will be made to parse the values as though it is
     87     * in the {@link TimeZone} represented by the {@code timeZone} parameter (if it is non-null).
     88     *
     89     * @param timeZone the time zone to use
     90     * @return A Date object representing when this image was modified, if possible, otherwise null
     91     */
     92    @Nullable
     93    public Date getDateModified(@Nullable TimeZone timeZone)
     94    {
     95        Directory parent = getParent();
     96        if (parent instanceof ExifIFD0Directory) {
     97            TimeZone timeZoneModified = getTimeZone(TAG_OFFSET_TIME);
     98            return parent.getDate(TAG_DATETIME, getString(TAG_SUBSECOND_TIME),
     99                (timeZoneModified != null) ? timeZoneModified : timeZone);
     100        } else {
     101            return null;
     102        }
     103    }
     104
     105    /**
     106     * Parses the date/time tag, the subsecond tag and the time offset tag to obtain a single Date
     107     * object with milliseconds representing the date and time when this image was captured.  If
     108     * the time offset tag does not exist, attempts will be made to parse the values as though it is
     109     * in the GMT {@link TimeZone}.
    72110     *
    73111     * @return A Date object representing when this image was captured, if possible, otherwise null
     
    80118
    81119    /**
    82      * Parses the date/time tag and the subsecond tag to obtain a single Date object with milliseconds
    83      * representing the date and time when this image was captured.  Attempts will be made to parse the
    84      * values as though it is in the {@link TimeZone} represented by the {@code timeZone} parameter
    85      * (if it is non-null).
     120     * Parses the date/time tag, the subsecond tag and the time offset tag to obtain a single Date
     121     * object with milliseconds representing the date and time when this image was captured.  If
     122     * the time offset tag does not exist, attempts will be made to parse the values as though it is
     123     * in the {@link TimeZone} represented by the {@code timeZone} parameter (if it is non-null).
    86124     *
    87125     * @param timeZone the time zone to use
     
    91129    public Date getDateOriginal(@Nullable TimeZone timeZone)
    92130    {
    93         return getDate(TAG_DATETIME_ORIGINAL, getString(TAG_SUBSECOND_TIME_ORIGINAL), timeZone);
     131        TimeZone timeZoneOriginal = getTimeZone(TAG_OFFSET_TIME_ORIGINAL);
     132        return getDate(TAG_DATETIME_ORIGINAL, getString(TAG_SUBSECOND_TIME_ORIGINAL),
     133            (timeZoneOriginal != null) ? timeZoneOriginal : timeZone);
    94134    }
    95135
    96136    /**
    97      * Parses the date/time tag and the subsecond tag to obtain a single Date object with milliseconds
    98      * representing the date and time when this image was digitized.  Attempts will be made to parse the
    99      * values as though it is in the GMT {@link TimeZone}.
     137     * Parses the date/time tag, the subsecond tag and the time offset tag to obtain a single Date
     138     * object with milliseconds representing the date and time when this image was digitized.  If
     139     * the time offset tag does not exist, attempts will be made to parse the values as though it is
     140     * in the GMT {@link TimeZone}.
    100141     *
    101142     * @return A Date object representing when this image was digitized, if possible, otherwise null
     
    108149
    109150    /**
    110      * Parses the date/time tag and the subsecond tag to obtain a single Date object with milliseconds
    111      * representing the date and time when this image was digitized.  Attempts will be made to parse the
    112      * values as though it is in the {@link TimeZone} represented by the {@code timeZone} parameter
    113      * (if it is non-null).
     151     * Parses the date/time tag, the subsecond tag and the time offset tag to obtain a single Date
     152     * object with milliseconds representing the date and time when this image was digitized.  If
     153     * the time offset tag does not exist, attempts will be made to parse the values as though it is
     154     * in the {@link TimeZone} represented by the {@code timeZone} parameter (if it is non-null).
    114155     *
    115156     * @param timeZone the time zone to use
     
    119160    public Date getDateDigitized(@Nullable TimeZone timeZone)
    120161    {
    121         return getDate(TAG_DATETIME_DIGITIZED, getString(TAG_SUBSECOND_TIME_DIGITIZED), timeZone);
     162        TimeZone timeZoneDigitized = getTimeZone(TAG_OFFSET_TIME_DIGITIZED);
     163        return getDate(TAG_DATETIME_DIGITIZED, getString(TAG_SUBSECOND_TIME_DIGITIZED),
     164            (timeZoneDigitized != null) ? timeZoneDigitized : timeZone);
     165    }
     166
     167    @Nullable
     168    private TimeZone getTimeZone(int tagType)
     169    {
     170        String timeOffset = getString(tagType);
     171        if (timeOffset != null && timeOffset.matches("[\\+\\-]\\d\\d:\\d\\d")) {
     172            return TimeZone.getTimeZone("GMT" + timeOffset);
     173        } else {
     174            return null;
     175        }
    122176    }
    123177}
  • trunk/src/com/drew/metadata/exif/ExifThumbnailDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2222package com.drew.metadata.exif;
    2323
    24 import static com.drew.metadata.exif.ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH;
    25 import static com.drew.metadata.exif.ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET;
    26 
    2724import com.drew.lang.annotations.NotNull;
    2825import com.drew.lang.annotations.Nullable;
     26
     27import static com.drew.metadata.exif.ExifThumbnailDirectory.*;
    2928
    3029/**
  • trunk/src/com/drew/metadata/exif/ExifThumbnailDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/ExifTiffHandler.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3030import com.drew.imaging.tiff.TiffProcessingException;
    3131import com.drew.imaging.tiff.TiffReader;
     32import com.drew.lang.BufferBoundsException;
    3233import com.drew.lang.Charsets;
    3334import com.drew.lang.RandomAccessReader;
     
    3839import com.drew.metadata.Metadata;
    3940import com.drew.metadata.StringValue;
    40 import com.drew.metadata.exif.makernotes.*;
     41import com.drew.metadata.exif.makernotes.AppleMakernoteDirectory;
     42import com.drew.metadata.exif.makernotes.CanonMakernoteDirectory;
     43import com.drew.metadata.exif.makernotes.CasioType1MakernoteDirectory;
     44import com.drew.metadata.exif.makernotes.CasioType2MakernoteDirectory;
     45import com.drew.metadata.exif.makernotes.FujifilmMakernoteDirectory;
     46import com.drew.metadata.exif.makernotes.KodakMakernoteDirectory;
     47import com.drew.metadata.exif.makernotes.KyoceraMakernoteDirectory;
     48import com.drew.metadata.exif.makernotes.LeicaMakernoteDirectory;
     49import com.drew.metadata.exif.makernotes.LeicaType5MakernoteDirectory;
     50import com.drew.metadata.exif.makernotes.NikonType1MakernoteDirectory;
     51import com.drew.metadata.exif.makernotes.NikonType2MakernoteDirectory;
     52import com.drew.metadata.exif.makernotes.OlympusCameraSettingsMakernoteDirectory;
     53import com.drew.metadata.exif.makernotes.OlympusEquipmentMakernoteDirectory;
     54import com.drew.metadata.exif.makernotes.OlympusFocusInfoMakernoteDirectory;
     55import com.drew.metadata.exif.makernotes.OlympusImageProcessingMakernoteDirectory;
     56import com.drew.metadata.exif.makernotes.OlympusMakernoteDirectory;
     57import com.drew.metadata.exif.makernotes.OlympusRawDevelopment2MakernoteDirectory;
     58import com.drew.metadata.exif.makernotes.OlympusRawDevelopmentMakernoteDirectory;
     59import com.drew.metadata.exif.makernotes.OlympusRawInfoMakernoteDirectory;
     60import com.drew.metadata.exif.makernotes.PanasonicMakernoteDirectory;
     61import com.drew.metadata.exif.makernotes.PentaxMakernoteDirectory;
     62import com.drew.metadata.exif.makernotes.ReconyxHyperFireMakernoteDirectory;
     63import com.drew.metadata.exif.makernotes.ReconyxUltraFireMakernoteDirectory;
     64import com.drew.metadata.exif.makernotes.RicohMakernoteDirectory;
     65import com.drew.metadata.exif.makernotes.SamsungType2MakernoteDirectory;
     66import com.drew.metadata.exif.makernotes.SanyoMakernoteDirectory;
     67import com.drew.metadata.exif.makernotes.SigmaMakernoteDirectory;
     68import com.drew.metadata.exif.makernotes.SonyType1MakernoteDirectory;
     69import com.drew.metadata.exif.makernotes.SonyType6MakernoteDirectory;
    4170import com.drew.metadata.iptc.IptcReader;
    4271import com.drew.metadata.tiff.DirectoryTiffHandler;
     
    5483    public ExifTiffHandler(@NotNull Metadata metadata, @Nullable Directory parentDirectory)
    5584    {
    56         super(metadata);
    57 
    58         if (parentDirectory != null)
    59             _currentDirectory.setParent(parentDirectory);
     85        super(metadata, parentDirectory);
    6086    }
    6187
     
    6793        final int panasonicRawTiffMarker = 0x0055; // for RW2 files
    6894
    69         switch (marker)
    70         {
     95        switch (marker) {
    7196            case standardTiffMarker:
    72             case olympusRawTiffMarker:      // Todo: implement an IFD0, if there is one
    73             case olympusRawTiffMarker2:     // Todo: implement an IFD0, if there is one
     97            case olympusRawTiffMarker:      // TODO implement an IFD0, if there is one
     98            case olympusRawTiffMarker2:     // TODO implement an IFD0, if there is one
    7499                pushDirectory(ExifIFD0Directory.class);
    75100                break;
     
    172197
    173198        // an unknown (0) formatCode needs to be potentially handled later as a highly custom directory tag
    174         if(formatCode == 0)
     199        if (formatCode == 0)
    175200            return 0L;
    176201
     
    185210                                    final int byteCount) throws IOException
    186211    {
     212        assert(_currentDirectory != null);
     213
    187214        // Some 0x0000 tags have a 0 byteCount. Determine whether it's bad.
    188         if (tagId == 0)
    189         {
    190             if (_currentDirectory.containsTag(tagId))
    191             {
     215        if (tagId == 0) {
     216            if (_currentDirectory.containsTag(tagId)) {
    192217                // Let it go through for now. Some directories handle it, some don't
    193218                return false;
     
    215240        }
    216241
    217         if (HandlePrintIM(_currentDirectory, tagId))
     242        if (handlePrintIM(_currentDirectory, tagId))
    218243        {
    219244            PrintIMDirectory printIMDirectory = new PrintIMDirectory();
    220245            printIMDirectory.setParent(_currentDirectory);
    221246            _metadata.addDirectory(printIMDirectory);
    222             ProcessPrintIM(printIMDirectory, tagOffset, reader, byteCount);
     247            processPrintIM(printIMDirectory, tagOffset, reader, byteCount);
    223248            return true;
    224249        }
     
    226251        // Note: these also appear in tryEnterSubIfd because some are IFD pointers while others begin immediately
    227252        // for the same directories
    228         if(_currentDirectory instanceof OlympusMakernoteDirectory)
    229         {
    230             switch (tagId)
    231             {
     253        if (_currentDirectory instanceof OlympusMakernoteDirectory) {
     254            switch (tagId) {
    232255                case OlympusMakernoteDirectory.TAG_EQUIPMENT:
    233256                    pushDirectory(OlympusEquipmentMakernoteDirectory.class);
     
    265288        }
    266289
    267         if (_currentDirectory instanceof PanasonicRawIFD0Directory)
    268         {
     290        if (_currentDirectory instanceof PanasonicRawIFD0Directory) {
    269291            // these contain binary data with specific offsets, and can't be processed as regular ifd's.
    270292            // The binary data is broken into 'fake' tags and there is a pattern.
    271             switch (tagId)
    272             {
     293            switch (tagId) {
    273294                case PanasonicRawIFD0Directory.TagWbInfo:
    274295                    PanasonicRawWbInfoDirectory dirWbInfo = new PanasonicRawWbInfoDirectory();
    275296                    dirWbInfo.setParent(_currentDirectory);
    276297                    _metadata.addDirectory(dirWbInfo);
    277                     ProcessBinary(dirWbInfo, tagOffset, reader, byteCount, false, 2);
     298                    processBinary(dirWbInfo, tagOffset, reader, byteCount, false, 2);
    278299                    return true;
    279300                case PanasonicRawIFD0Directory.TagWbInfo2:
     
    281302                    dirWbInfo2.setParent(_currentDirectory);
    282303                    _metadata.addDirectory(dirWbInfo2);
    283                     ProcessBinary(dirWbInfo2, tagOffset, reader, byteCount, false, 3);
     304                    processBinary(dirWbInfo2, tagOffset, reader, byteCount, false, 3);
    284305                    return true;
    285306                case PanasonicRawIFD0Directory.TagDistortionInfo:
     
    287308                    dirDistort.setParent(_currentDirectory);
    288309                    _metadata.addDirectory(dirDistort);
    289                     ProcessBinary(dirDistort, tagOffset, reader, byteCount, true, 1);
     310                    processBinary(dirDistort, tagOffset, reader, byteCount, true, 1);
    290311                    return true;
    291312            }
     
    293314
    294315        // Panasonic RAW sometimes contains an embedded version of the data as a JPG file.
    295         if (tagId == PanasonicRawIFD0Directory.TagJpgFromRaw && _currentDirectory instanceof PanasonicRawIFD0Directory)
    296         {
     316        if (tagId == PanasonicRawIFD0Directory.TagJpgFromRaw && _currentDirectory instanceof PanasonicRawIFD0Directory) {
    297317            byte[] jpegrawbytes = reader.getBytes(tagOffset, byteCount);
    298318
     
    316336    }
    317337
    318     private static void ProcessBinary(@NotNull final Directory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader, final int byteCount, final Boolean issigned, final int arrayLength) throws IOException
     338    private static void processBinary(@NotNull final Directory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader, final int byteCount, final Boolean isSigned, final int arrayLength) throws IOException
    319339    {
    320340        // expects signed/unsigned int16 (for now)
    321         //int byteSize = issigned ? sizeof(short) : sizeof(ushort);
     341        //int byteSize = isSigned ? sizeof(short) : sizeof(ushort);
    322342        int byteSize = 2;
    323343
    324344        // 'directory' is assumed to contain tags that correspond to the byte position unless it's a set of bytes
    325         for (int i = 0; i < byteCount; i++)
    326         {
    327             if (directory.hasTagName(i))
    328             {
     345        for (int i = 0; i < byteCount; i++) {
     346            if (directory.hasTagName(i)) {
    329347                // only process this tag if the 'next' integral tag exists. Otherwise, it's a set of bytes
    330                 if (i < byteCount - 1 && directory.hasTagName(i + 1))
    331                 {
    332                     if(issigned)
     348                if (i < byteCount - 1 && directory.hasTagName(i + 1)) {
     349                    if (isSigned)
    333350                        directory.setObject(i, reader.getInt16(tagValueOffset + (i* byteSize)));
    334351                    else
    335352                        directory.setObject(i, reader.getUInt16(tagValueOffset + (i* byteSize)));
    336                 }
    337                 else
    338                 {
     353                } else {
    339354                    // the next arrayLength bytes are a multi-byte value
    340                     if (issigned)
    341                     {
     355                    if (isSigned) {
    342356                        short[] val = new short[arrayLength];
    343357                        for (int j = 0; j<val.length; j++)
    344358                            val[j] = reader.getInt16(tagValueOffset + ((i + j) * byteSize));
    345359                        directory.setObjectArray(i, val);
    346                     }
    347                     else
    348                     {
     360                    } else {
    349361                        int[] val = new int[arrayLength];
    350362                        for (int j = 0; j<val.length; j++)
     
    359371    }
    360372
     373    /** Read a given number of bytes from the stream
     374     *
     375     * This method is employed to "suppress" attempts to read beyond end of the
     376     * file as may happen at the beginning of processMakernote when we read
     377     * increasingly longer camera makes.
     378     *
     379     * Instead of failing altogether in this context we return an empty string
     380     * which will fail all sensible attempts to compare to makes while avoiding
     381     * a full-on failure.
     382     */
     383    @NotNull
     384    private static String getReaderString(final @NotNull RandomAccessReader reader, final int makernoteOffset, final int bytesRequested) throws IOException
     385    {
     386        try {
     387            return reader.getString(makernoteOffset, bytesRequested, Charsets.UTF_8);
     388        } catch(BufferBoundsException e) {
     389            return "";
     390        }
     391    }
     392
    361393    private boolean processMakernote(final int makernoteOffset,
    362394                                     final @NotNull Set<Integer> processedIfdOffsets,
     
    364396                                     final @NotNull RandomAccessReader reader) throws IOException
    365397    {
     398        assert(_currentDirectory != null);
     399
    366400        // Determine the camera model and makernote format.
    367401        Directory ifd0Directory = _metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
     
    369403        String cameraMake = ifd0Directory == null ? null : ifd0Directory.getString(ExifIFD0Directory.TAG_MAKE);
    370404
    371         final String firstTwoChars    = reader.getString(makernoteOffset, 2, Charsets.UTF_8);
    372         final String firstThreeChars  = reader.getString(makernoteOffset, 3, Charsets.UTF_8);
    373         final String firstFourChars   = reader.getString(makernoteOffset, 4, Charsets.UTF_8);
    374         final String firstFiveChars   = reader.getString(makernoteOffset, 5, Charsets.UTF_8);
    375         final String firstSixChars    = reader.getString(makernoteOffset, 6, Charsets.UTF_8);
    376         final String firstSevenChars  = reader.getString(makernoteOffset, 7, Charsets.UTF_8);
    377         final String firstEightChars  = reader.getString(makernoteOffset, 8, Charsets.UTF_8);
    378         final String firstNineChars   = reader.getString(makernoteOffset, 9, Charsets.UTF_8);
    379         final String firstTenChars    = reader.getString(makernoteOffset, 10, Charsets.UTF_8);
    380         final String firstTwelveChars = reader.getString(makernoteOffset, 12, Charsets.UTF_8);
     405        final String firstTwoChars    = getReaderString(reader, makernoteOffset, 2);
     406        final String firstThreeChars  = getReaderString(reader, makernoteOffset, 3);
     407        final String firstFourChars   = getReaderString(reader, makernoteOffset, 4);
     408        final String firstFiveChars   = getReaderString(reader, makernoteOffset, 5);
     409        final String firstSixChars    = getReaderString(reader, makernoteOffset, 6);
     410        final String firstSevenChars  = getReaderString(reader, makernoteOffset, 7);
     411        final String firstEightChars  = getReaderString(reader, makernoteOffset, 8);
     412        final String firstNineChars   = getReaderString(reader, makernoteOffset, 9);
     413        final String firstTenChars    = getReaderString(reader, makernoteOffset, 10);
     414        final String firstTwelveChars = getReaderString(reader, makernoteOffset, 12);
    381415
    382416        boolean byteOrderBefore = reader.isMotorolaByteOrder();
     
    501535                return false;
    502536            }
    503         } else if ("Panasonic\u0000\u0000\u0000".equals(reader.getString(makernoteOffset, 12, Charsets.UTF_8))) {
     537        } else if ("Panasonic\u0000\u0000\u0000".equals(firstTwelveChars)) {
    504538            // NON-Standard TIFF IFD Data using Panasonic Tags. There is no Next-IFD pointer after the IFD
    505539            // Offsets are relative to the start of the TIFF header at the beginning of the EXIF segment
     
    575609    }
    576610
    577     private static Boolean HandlePrintIM(@NotNull final Directory directory, final int tagId)
     611    private static boolean handlePrintIM(@NotNull final Directory directory, final int tagId)
    578612    {
    579613        if (tagId == ExifDirectoryBase.TAG_PRINT_IMAGE_MATCHING_INFO)
    580614            return true;
    581615
    582         if (tagId == 0x0E00)
    583         {
     616        if (tagId == 0x0E00) {
    584617            // Tempting to say every tagid of 0x0E00 is a PIM tag, but can't be 100% sure
    585618            if (directory instanceof CasioType2MakernoteDirectory ||
     
    606639    /// lib\Image\ExifTool\PrintIM.pm
    607640    /// </remarks>
    608     private static void ProcessPrintIM(@NotNull final PrintIMDirectory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader, final int byteCount) throws IOException
     641    private static void processPrintIM(@NotNull final PrintIMDirectory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader, final int byteCount) throws IOException
    609642    {
    610643        Boolean resetByteOrder = null;
    611644
    612         if (byteCount == 0)
    613         {
     645        if (byteCount == 0) {
    614646            directory.addError("Empty PrintIM data");
    615647            return;
    616648        }
    617649
    618         if (byteCount <= 15)
    619         {
     650        if (byteCount <= 15) {
    620651            directory.addError("Bad PrintIM data");
    621652            return;
     
    624655        String header = reader.getString(tagValueOffset, 12, Charsets.UTF_8);
    625656
    626         if (!header.startsWith("PrintIM")) //, StringComparison.Ordinal))
    627         {
     657        if (!header.startsWith("PrintIM")) {
    628658            directory.addError("Invalid PrintIM header");
    629659            return;
     
    632662        // check size of PrintIM block
    633663        int num = reader.getUInt16(tagValueOffset + 14);
    634         if (byteCount < 16 + num * 6)
    635         {
     664
     665        if (byteCount < 16 + num * 6) {
    636666            // size is too big, maybe byte ordering is wrong
    637667            resetByteOrder = reader.isMotorolaByteOrder();
    638668            reader.setMotorolaByteOrder(!reader.isMotorolaByteOrder());
    639669            num = reader.getUInt16(tagValueOffset + 14);
    640             if (byteCount < 16 + num * 6)
    641             {
     670            if (byteCount < 16 + num * 6) {
    642671                directory.addError("Bad PrintIM size");
    643672                return;
     
    647676        directory.setObject(PrintIMDirectory.TagPrintImVersion, header.substring(8, 12));
    648677
    649         for (int n = 0; n < num; n++)
    650         {
     678        for (int n = 0; n < num; n++) {
    651679            int pos = tagValueOffset + 16 + n * 6;
    652680            int tag = reader.getUInt16(pos);
     
    712740            build = null;
    713741        }
    714         if (build != null)
    715         {
     742
     743        if (build != null) {
    716744            directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION, String.format("%d.%d.%d.%s", major, minor, revision, build));
    717         }
    718         else
    719         {
     745        } else {
    720746            directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION, String.format("%d.%d.%d", major, minor, revision));
    721747            directory.addError("Error processing Reconyx HyperFire makernote data: build '" + buildYearAndDate + "' is not in the expected format and will be omitted from Firmware Version.");
     
    746772            (month >= 1 && month < 13) &&
    747773            (day >= 1 && day < 32) &&
    748             (year >= 1 && year <= 9999))
    749         {
     774            (year >= 1 && year <= 9999)) {
    750775            directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL,
    751776                    String.format("%4d:%2d:%2d %2d:%2d:%2d", year, month, day, hour, minutes, seconds));
    752         }
    753         else
    754         {
     777        } else {
    755778            directory.addError("Error processing Reconyx HyperFire makernote data: Date/Time Original " + year + "-" + month + "-" + day + " " + hour + ":" + minutes + ":" + seconds + " is not a valid date/time.");
    756779        }
     
    778801        /*uint makernoteID = ByteConvert.FromBigEndianToNative(reader.GetUInt32(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagMakernoteID));
    779802        directory.Set(ReconyxUltraFireMakernoteDirectory.TagMakernoteID, makernoteID);
    780         if (makernoteID != ReconyxUltraFireMakernoteDirectory.MAKERNOTE_ID)
    781         {
     803        if (makernoteID != ReconyxUltraFireMakernoteDirectory.MAKERNOTE_ID) {
    782804            directory.addError("Error processing Reconyx UltraFire makernote data: unknown Makernote ID 0x" + makernoteID.ToString("x8"));
    783805            return;
     
    786808        uint makernotePublicID = ByteConvert.FromBigEndianToNative(reader.GetUInt32(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagMakernotePublicID));
    787809        directory.Set(ReconyxUltraFireMakernoteDirectory.TagMakernotePublicID, makernotePublicID);
    788         if (makernotePublicID != ReconyxUltraFireMakernoteDirectory.MAKERNOTE_PUBLIC_ID)
    789         {
     810        if (makernotePublicID != ReconyxUltraFireMakernoteDirectory.MAKERNOTE_PUBLIC_ID) {
    790811            directory.addError("Error processing Reconyx UltraFire makernote data: unknown Makernote Public ID 0x" + makernotePublicID.ToString("x8"));
    791812            return;
     
    818839            (month >= 1 && month < 13) &&
    819840            (day >= 1 && day < 32) &&
    820             (year >= 1 && year <= 9999))
    821         {
     841            (year >= 1 && year <= 9999)) {
    822842            directory.Set(ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL, new DateTime(year, month, day, hour, minutes, seconds, DateTimeKind.Unspecified));
    823         }
    824         else
    825         {
     843        } else {
    826844            directory.addError("Error processing Reconyx UltraFire makernote data: Date/Time Original " + year + "-" + month + "-" + day + " " + hour + ":" + minutes + ":" + seconds + " is not a valid date/time.");
    827845        }*/
  • trunk/src/com/drew/metadata/exif/GpsDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    5959            case TAG_MEASURE_MODE:
    6060                return getGpsMeasureModeDescription();
     61            case TAG_DOP:
     62                return getGpsDopDescription();
    6163            case TAG_SPEED_REF:
    6264                return getGpsSpeedRefDescription();
     65            case TAG_SPEED:
     66                return getGpsSpeedDescription();
    6367            case TAG_TRACK_REF:
    6468            case TAG_IMG_DIRECTION_REF:
     
    6973            case TAG_DEST_BEARING:
    7074                return getGpsDirectionDescription(tagType);
     75            case TAG_DEST_LATITUDE:
     76                return getGpsDestLatitudeDescription();
     77            case TAG_DEST_LONGITUDE:
     78                return getGpsDestLongitudeDescription();
    7179            case TAG_DEST_DISTANCE_REF:
    7280                return getGpsDestinationReferenceDescription();
     81            case TAG_DEST_DISTANCE:
     82                return getGpsDestDistanceDescription();
    7383            case TAG_TIME_STAMP:
    7484                return getGpsTimeStampDescription();
     
    7989                // three rational numbers -- displayed in HH"MM"SS.ss
    8090                return getGpsLatitudeDescription();
     91            case TAG_PROCESSING_METHOD:
     92                return getGpsProcessingMethodDescription();
     93            case TAG_AREA_INFORMATION:
     94                return getGpsAreaInformationDescription();
    8195            case TAG_DIFFERENTIAL:
    8296                return getGpsDifferentialDescription();
     97            case TAG_H_POSITIONING_ERROR:
     98                return getGpsHPositioningErrorDescription();
    8399            default:
    84100                return super.getDescription(tagType);
     
    121137
    122138    @Nullable
     139    public String getGpsDestLatitudeDescription()
     140    {
     141        Rational[] latitudes = _directory.getRationalArray(TAG_DEST_LATITUDE);
     142        String latitudeRef = _directory.getString(TAG_DEST_LATITUDE_REF);
     143
     144        if (latitudes == null || latitudes.length != 3 || latitudeRef == null)
     145            return null;
     146
     147        Double lat = GeoLocation.degreesMinutesSecondsToDecimal(
     148            latitudes[0], latitudes[1], latitudes[2], latitudeRef.equalsIgnoreCase("S"));
     149
     150        return lat == null ? null : GeoLocation.decimalToDegreesMinutesSecondsString(lat);
     151    }
     152
     153    @Nullable
     154    public String getGpsDestLongitudeDescription()
     155    {
     156        Rational[] longitudes = _directory.getRationalArray(TAG_LONGITUDE);
     157        String longitudeRef = _directory.getString(TAG_LONGITUDE_REF);
     158
     159        if (longitudes == null || longitudes.length != 3 || longitudeRef == null)
     160            return null;
     161
     162        Double lon = GeoLocation.degreesMinutesSecondsToDecimal(
     163            longitudes[0], longitudes[1], longitudes[2], longitudeRef.equalsIgnoreCase("W"));
     164
     165        return lon == null ? null : GeoLocation.decimalToDegreesMinutesSecondsString(lon);
     166    }
     167
     168    @Nullable
    123169    public String getGpsDestinationReferenceDescription()
    124170    {
     
    139185
    140186    @Nullable
     187    public String getGpsDestDistanceDescription()
     188    {
     189        final Rational value = _directory.getRational(TAG_DEST_DISTANCE);
     190        if (value == null)
     191            return null;
     192        final String unit = getGpsDestinationReferenceDescription();
     193        return String.format("%s %s",
     194            new DecimalFormat("0.##").format(value.doubleValue()),
     195            unit == null ? "unit" : unit.toLowerCase());
     196    }
     197
     198    @Nullable
    141199    public String getGpsDirectionDescription(int tagType)
    142200    {
     
    166224
    167225    @Nullable
     226    public String getGpsDopDescription()
     227    {
     228        final Rational value = _directory.getRational(TAG_DOP);
     229        return value == null ? null : new DecimalFormat("0.##").format(value.doubleValue());
     230    }
     231
     232    @Nullable
    168233    public String getGpsSpeedRefDescription()
    169234    {
     
    173238        String gpsSpeedRef = value.trim();
    174239        if ("K".equalsIgnoreCase(gpsSpeedRef)) {
    175             return "kph";
     240            return "km/h";
    176241        } else if ("M".equalsIgnoreCase(gpsSpeedRef)) {
    177242            return "mph";
     
    181246            return "Unknown (" + gpsSpeedRef + ")";
    182247        }
     248    }
     249
     250    @Nullable
     251    public String getGpsSpeedDescription()
     252    {
     253        final Rational value = _directory.getRational(TAG_SPEED);
     254        if (value == null)
     255            return null;
     256        final String unit = getGpsSpeedRefDescription();
     257        return String.format("%s %s",
     258            new DecimalFormat("0.##").format(value.doubleValue()),
     259            unit == null ? "unit" : unit.toLowerCase());
    183260    }
    184261
     
    225302    {
    226303        final Rational value = _directory.getRational(TAG_ALTITUDE);
    227         return value == null ? null : value.intValue() + " metres";
     304        return value == null ? null : new DecimalFormat("0.##").format(value.doubleValue()) + " metres";
     305    }
     306
     307    @Nullable
     308    public String getGpsProcessingMethodDescription()
     309    {
     310        return getEncodedTextDescription(TAG_PROCESSING_METHOD);
     311    }
     312
     313    @Nullable
     314    public String getGpsAreaInformationDescription()
     315    {
     316        return getEncodedTextDescription(TAG_AREA_INFORMATION);
    228317    }
    229318
     
    232321    {
    233322        return getIndexedDescription(TAG_DIFFERENTIAL, "No Correction", "Differential Corrected");
     323    }
     324
     325    @Nullable
     326    public String getGpsHPositioningErrorDescription()
     327    {
     328        final Rational value = _directory.getRational(TAG_H_POSITIONING_ERROR);
     329        return value == null ? null : new DecimalFormat("0.##").format(value.doubleValue()) + " metres";
    234330    }
    235331
  • trunk/src/com/drew/metadata/exif/GpsDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    9595    /** Distance to destination GPSDestDistance 26 1A RATIONAL 1 */
    9696    public static final int TAG_DEST_DISTANCE = 0x001A;
    97 
    98     /** Values of "GPS", "CELLID", "WLAN" or "MANUAL" by the EXIF spec. */
     97    /** Name of the method used for location finding GPSProcessingMethod 27 1B UNDEFINED Any */
    9998    public static final int TAG_PROCESSING_METHOD = 0x001B;
     99    /** Name of the GPS area GPSAreaInformation 28 1C UNDEFINED Any */
    100100    public static final int TAG_AREA_INFORMATION = 0x001C;
     101    /** Date and time GPSDateStamp 29 1D ASCII 11 */
    101102    public static final int TAG_DATE_STAMP = 0x001D;
     103    /** Whether differential correction is applied GPSDifferential 30 1E SHORT 1 */
    102104    public static final int TAG_DIFFERENTIAL = 0x001E;
     105    /** Horizontal positioning errors GPSHPositioningError 31 1F RATIONAL 1 */
     106    public static final int TAG_H_POSITIONING_ERROR = 0x001F;
    103107
    104108    @NotNull
     
    140144        _tagNameMap.put(TAG_DATE_STAMP, "GPS Date Stamp");
    141145        _tagNameMap.put(TAG_DIFFERENTIAL, "GPS Differential");
     146        _tagNameMap.put(TAG_H_POSITIONING_ERROR, "GPS H Positioning Error");
    142147    }
    143148
  • trunk/src/com/drew/metadata/exif/PanasonicRawDistortionDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/PanasonicRawDistortionDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/PanasonicRawIFD0Descriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/PanasonicRawIFD0Directory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/PanasonicRawWbInfo2Descriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/PanasonicRawWbInfo2Directory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/PanasonicRawWbInfoDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/PanasonicRawWbInfoDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/PrintIMDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/PrintIMDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/AppleMakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/AppleMakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/CanonMakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/CanonMakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/CasioType1MakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/CasioType1MakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/CasioType2MakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/CasioType2MakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/FujifilmMakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/FujifilmMakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/KodakMakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/KodakMakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/KyoceraMakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/KyoceraMakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/LeicaMakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/LeicaMakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/LeicaType5MakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/LeicaType5MakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/NikonType1MakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/NikonType1MakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/NikonType2MakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2626import com.drew.metadata.TagDescriptor;
    2727
     28import java.nio.ByteBuffer;
    2829import java.text.DecimalFormat;
    2930
     
    105106    public String getPowerUpTimeDescription()
    106107    {
    107         return getEpochTimeDescription(TAG_POWER_UP_TIME);
     108        // this is generally a byte[] of length 8 directly representing a date and time.
     109        // the format is : first 2 bytes together are the year, and then each byte after
     110        //                 is month, day, hour, minute, second with the eighth byte unused
     111        // e.g., 2011:04:25 01:54:58
     112
     113        byte[] values = _directory.getByteArray(TAG_POWER_UP_TIME);
     114        short year = ByteBuffer.wrap(new byte[]{values[0], values[1]}).getShort();
     115        return String.format("%04d:%02d:%02d %02d:%02d:%02d", year, values[2], values[3],
     116                                                        values[4], values[5], values[6]);
    108117    }
    109118
     
    334343
    335344    @Nullable
     345    public String getLensFocusDistance()
     346    {
     347        int[] values = _directory.getDecryptedIntArray(TAG_LENS_DATA);
     348
     349        if (values == null || values.length < 11)
     350            return null;
     351
     352        return String.format("%.2fm", getDistanceInMeters(values[10]));
     353    }
     354
     355    @Nullable
    336356    public String getHueAdjustmentDescription()
    337357    {
     
    351371        return getVersionBytesDescription(TAG_FIRMWARE_VERSION, 2);
    352372    }
     373
     374    private double getDistanceInMeters(int val)
     375    {
     376        if (val < 0)
     377            val += 256;
     378        return 0.01 * Math.pow(10, val / 40.0f);
     379    }
    353380}
  • trunk/src/com/drew/metadata/exif/makernotes/NikonType2MakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2121package com.drew.metadata.exif.makernotes;
    2222
     23import com.drew.lang.annotations.Nullable;
    2324import com.drew.lang.annotations.NotNull;
    2425import com.drew.metadata.Directory;
     
    923924        return _tagNameMap;
    924925    }
     926
     927        /** Nikon decryption tables used in exiftool */
     928    private static final int[] _decTable1 =   {0xc1,0xbf,0x6d,0x0d,0x59,0xc5,0x13,0x9d,0x83,0x61,0x6b,0x4f,0xc7,0x7f,0x3d,0x3d,
     929                                               0x53,0x59,0xe3,0xc7,0xe9,0x2f,0x95,0xa7,0x95,0x1f,0xdf,0x7f,0x2b,0x29,0xc7,0x0d,
     930                                               0xdf,0x07,0xef,0x71,0x89,0x3d,0x13,0x3d,0x3b,0x13,0xfb,0x0d,0x89,0xc1,0x65,0x1f,
     931                                               0xb3,0x0d,0x6b,0x29,0xe3,0xfb,0xef,0xa3,0x6b,0x47,0x7f,0x95,0x35,0xa7,0x47,0x4f,
     932                                               0xc7,0xf1,0x59,0x95,0x35,0x11,0x29,0x61,0xf1,0x3d,0xb3,0x2b,0x0d,0x43,0x89,0xc1,
     933                                               0x9d,0x9d,0x89,0x65,0xf1,0xe9,0xdf,0xbf,0x3d,0x7f,0x53,0x97,0xe5,0xe9,0x95,0x17,
     934                                               0x1d,0x3d,0x8b,0xfb,0xc7,0xe3,0x67,0xa7,0x07,0xf1,0x71,0xa7,0x53,0xb5,0x29,0x89,
     935                                               0xe5,0x2b,0xa7,0x17,0x29,0xe9,0x4f,0xc5,0x65,0x6d,0x6b,0xef,0x0d,0x89,0x49,0x2f,
     936                                               0xb3,0x43,0x53,0x65,0x1d,0x49,0xa3,0x13,0x89,0x59,0xef,0x6b,0xef,0x65,0x1d,0x0b,
     937                                               0x59,0x13,0xe3,0x4f,0x9d,0xb3,0x29,0x43,0x2b,0x07,0x1d,0x95,0x59,0x59,0x47,0xfb,
     938                                               0xe5,0xe9,0x61,0x47,0x2f,0x35,0x7f,0x17,0x7f,0xef,0x7f,0x95,0x95,0x71,0xd3,0xa3,
     939                                               0x0b,0x71,0xa3,0xad,0x0b,0x3b,0xb5,0xfb,0xa3,0xbf,0x4f,0x83,0x1d,0xad,0xe9,0x2f,
     940                                               0x71,0x65,0xa3,0xe5,0x07,0x35,0x3d,0x0d,0xb5,0xe9,0xe5,0x47,0x3b,0x9d,0xef,0x35,
     941                                               0xa3,0xbf,0xb3,0xdf,0x53,0xd3,0x97,0x53,0x49,0x71,0x07,0x35,0x61,0x71,0x2f,0x43,
     942                                               0x2f,0x11,0xdf,0x17,0x97,0xfb,0x95,0x3b,0x7f,0x6b,0xd3,0x25,0xbf,0xad,0xc7,0xc5,
     943                                               0xc5,0xb5,0x8b,0xef,0x2f,0xd3,0x07,0x6b,0x25,0x49,0x95,0x25,0x49,0x6d,0x71,0xc7 };
     944    private static final int[] _decTable2 = { 0xa7,0xbc,0xc9,0xad,0x91,0xdf,0x85,0xe5,0xd4,0x78,0xd5,0x17,0x46,0x7c,0x29,0x4c,
     945                                               0x4d,0x03,0xe9,0x25,0x68,0x11,0x86,0xb3,0xbd,0xf7,0x6f,0x61,0x22,0xa2,0x26,0x34,
     946                                               0x2a,0xbe,0x1e,0x46,0x14,0x68,0x9d,0x44,0x18,0xc2,0x40,0xf4,0x7e,0x5f,0x1b,0xad,
     947                                               0x0b,0x94,0xb6,0x67,0xb4,0x0b,0xe1,0xea,0x95,0x9c,0x66,0xdc,0xe7,0x5d,0x6c,0x05,
     948                                               0xda,0xd5,0xdf,0x7a,0xef,0xf6,0xdb,0x1f,0x82,0x4c,0xc0,0x68,0x47,0xa1,0xbd,0xee,
     949                                               0x39,0x50,0x56,0x4a,0xdd,0xdf,0xa5,0xf8,0xc6,0xda,0xca,0x90,0xca,0x01,0x42,0x9d,
     950                                               0x8b,0x0c,0x73,0x43,0x75,0x05,0x94,0xde,0x24,0xb3,0x80,0x34,0xe5,0x2c,0xdc,0x9b,
     951                                               0x3f,0xca,0x33,0x45,0xd0,0xdb,0x5f,0xf5,0x52,0xc3,0x21,0xda,0xe2,0x22,0x72,0x6b,
     952                                               0x3e,0xd0,0x5b,0xa8,0x87,0x8c,0x06,0x5d,0x0f,0xdd,0x09,0x19,0x93,0xd0,0xb9,0xfc,
     953                                               0x8b,0x0f,0x84,0x60,0x33,0x1c,0x9b,0x45,0xf1,0xf0,0xa3,0x94,0x3a,0x12,0x77,0x33,
     954                                               0x4d,0x44,0x78,0x28,0x3c,0x9e,0xfd,0x65,0x57,0x16,0x94,0x6b,0xfb,0x59,0xd0,0xc8,
     955                                               0x22,0x36,0xdb,0xd2,0x63,0x98,0x43,0xa1,0x04,0x87,0x86,0xf7,0xa6,0x26,0xbb,0xd6,
     956                                               0x59,0x4d,0xbf,0x6a,0x2e,0xaa,0x2b,0xef,0xe6,0x78,0xb6,0x4e,0xe0,0x2f,0xdc,0x7c,
     957                                               0xbe,0x57,0x19,0x32,0x7e,0x2a,0xd0,0xb8,0xba,0x29,0x00,0x3c,0x52,0x7d,0xa8,0x49,
     958                                               0x3b,0x2d,0xeb,0x25,0x49,0xfa,0xa3,0xaa,0x39,0xa7,0xc5,0xa7,0x50,0x11,0x36,0xfb,
     959                                               0xc6,0x67,0x4a,0xf5,0xa5,0x12,0x65,0x7e,0xb0,0xdf,0xaf,0x4e,0xb3,0x61,0x7f,0x2f };
     960
     961
     962    /** decryption algorithm adapted from exiftool */
     963    @Nullable
     964    public int[] getDecryptedIntArray(int tagType)
     965    {
     966        int[] data = getIntArray(tagType);
     967        Integer serial = getInteger(TAG_CAMERA_SERIAL_NUMBER);
     968        Integer count = getInteger(TAG_EXPOSURE_SEQUENCE_NUMBER);
     969
     970        if (data == null || serial == null || count == null)
     971            return null;
     972
     973        int key = 0;
     974        for (int i = 0; i < 4; i++)
     975            key ^= (count >> (i * 8)) & 0xff;
     976
     977        int ci = _decTable1[serial & 0xff];
     978        int cj = _decTable2[key];
     979        int ck = 0x60;
     980
     981        for (int i = 4; i < data.length; i++)
     982        {
     983            cj = (cj + ci * ck) & 0xff;
     984            ck = (ck + 1) & 0xff;
     985            data[i] ^= cj;
     986        }
     987
     988        return data;
     989    }
    925990}
  • trunk/src/com/drew/metadata/exif/makernotes/OlympusMakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/OlympusMakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/OlympusRawDevelopment2MakernoteDescriptor.java

    r13061 r15217  
    111111        if (((v >> 1) & 1) != 0) sb.append("Noise Filter, ");
    112112        if (((v >> 2) & 1) != 0) sb.append("Noise Filter (ISO Boost), ");
    113 
    114         return sb.substring(0, sb.length() - 2);
     113        if (((v >> 3) & 1) != 0) sb.append("Noise Filter (Auto), ");
     114       
     115        if (sb.length() > 2) {
     116            sb.delete(sb.length() - 2, sb.length());
     117        }
     118        return sb.toString();
    115119    }
    116120
  • trunk/src/com/drew/metadata/exif/makernotes/PanasonicMakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/PanasonicMakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/PentaxMakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/PentaxMakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/ReconyxHyperFireMakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/ReconyxHyperFireMakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/ReconyxUltraFireMakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/ReconyxUltraFireMakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/RicohMakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/RicohMakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/SamsungType2MakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/SamsungType2MakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/SanyoMakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/SanyoMakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/SigmaMakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/SigmaMakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/SonyType1MakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/SonyType1MakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/SonyType6MakernoteDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/SonyType6MakernoteDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/iptc/IptcDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/iptc/IptcDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/iptc/IptcReader.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    118118            }
    119119
    120             // we need at least five bytes left to read a tag
    121             if (offset + 5 > length) {
     120            // we need at least four bytes left to read a tag
     121            if (offset + 4 > length) {
    122122                directory.addError("Too few bytes remain for a valid IPTC tag");
    123123                return;
     
    130130                directoryType = reader.getUInt8();
    131131                tagType = reader.getUInt8();
    132                 // TODO support Extended DataSet Tag (see 1.5(c), p14, IPTC-IIMV4.2.pdf)
    133132                tagByteCount = reader.getUInt16();
     133                if (tagByteCount > 32767) {
     134                    // Extended DataSet Tag (see 1.5(c), p14, IPTC-IIMV4.2.pdf)
     135                    tagByteCount = ((tagByteCount & 0x7FFF) << 16) | reader.getUInt16();
     136                    offset += 2;
     137                }
    134138                offset += 4;
    135139            } catch (IOException e) {
  • trunk/src/com/drew/metadata/iptc/Iso2022Converter.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/jpeg/HuffmanTablesDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2828
    2929/**
    30  * Provides a human-readable string version of the tag stored in a HuffmanTableDirectory.
     30 * Provides a human-readable string version of the tag stored in a {@link HuffmanTablesDirectory}.
    3131 *
    3232 * <ul>
     
    6161    {
    6262        Integer value = _directory.getInteger(TAG_NUMBER_OF_TABLES);
    63         if (value==null)
     63        if (value == null)
    6464            return null;
    6565        return value + (value == 1 ? " Huffman table" : " Huffman tables");
  • trunk/src/com/drew/metadata/jpeg/HuffmanTablesDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2525import java.util.HashMap;
    2626import java.util.List;
     27
    2728import com.drew.lang.annotations.NotNull;
    2829import com.drew.metadata.Directory;
     
    168169    /**
    169170     * @return The {@link List} of {@link HuffmanTable}s in this
    170      *         {@link Directory}.
     171     * {@link Directory}.
    171172     */
    172173    @NotNull
     
    227228     */
    228229    public static class HuffmanTable {
    229         private final int tableLength;
    230         private final HuffmanTableClass tableClass;
    231         private final int tableDestinationId;
    232         private final byte[] lengthBytes;
    233         private final byte[] valueBytes;
    234 
    235         public HuffmanTable (
    236             @NotNull HuffmanTableClass
    237             tableClass,
     230        private final int _tableLength;
     231        private final HuffmanTableClass _tableClass;
     232        private final int _tableDestinationId;
     233        private final byte[] _lengthBytes;
     234        private final byte[] _valueBytes;
     235
     236        @SuppressWarnings("ConstantConditions")
     237        public HuffmanTable(
     238            @NotNull HuffmanTableClass tableClass,
    238239            int tableDestinationId,
    239             @NotNull byte[] lBytes,
    240             @NotNull byte[] vBytes
    241         ) {
    242             this.tableClass = tableClass;
    243             this.tableDestinationId = tableDestinationId;
    244             this.lengthBytes = lBytes;
    245             this.valueBytes = vBytes;
    246             this.tableLength = vBytes.length + 17;
     240            @NotNull byte[] lengthBytes,
     241            @NotNull byte[] valueBytes)
     242        {
     243            if (lengthBytes == null)
     244                throw new IllegalArgumentException("lengthBytes cannot be null.");
     245            if (valueBytes == null)
     246                throw new IllegalArgumentException("valueBytes cannot be null.");
     247
     248            _tableClass = tableClass;
     249            _tableDestinationId = tableDestinationId;
     250            _lengthBytes = lengthBytes;
     251            _valueBytes = valueBytes;
     252            _tableLength = _valueBytes.length + 17;
    247253        }
    248254
     
    251257         */
    252258        public int getTableLength() {
    253             return tableLength;
    254         }
    255 
     259            return _tableLength;
     260        }
    256261
    257262        /**
     
    259264         */
    260265        public HuffmanTableClass getTableClass() {
    261             return tableClass;
    262         }
    263 
     266            return _tableClass;
     267        }
    264268
    265269        /**
     
    267271         */
    268272        public int getTableDestinationId() {
    269             return tableDestinationId;
    270         }
    271 
     273            return _tableDestinationId;
     274        }
    272275
    273276        /**
    274277         * @return A byte array with the L values for this table.
    275278         */
     279        @NotNull
    276280        public byte[] getLengthBytes() {
    277             if (lengthBytes == null)
    278                 return null;
    279             byte[] result = new byte[lengthBytes.length];
    280             System.arraycopy(lengthBytes, 0, result, 0, lengthBytes.length);
     281            byte[] result = new byte[_lengthBytes.length];
     282            System.arraycopy(_lengthBytes, 0, result, 0, _lengthBytes.length);
    281283            return result;
    282284        }
    283285
    284 
    285286        /**
    286287         * @return A byte array with the V values for this table.
    287288         */
     289        @NotNull
    288290        public byte[] getValueBytes() {
    289             if (valueBytes == null)
    290                 return null;
    291             byte[] result = new byte[valueBytes.length];
    292             System.arraycopy(valueBytes, 0, result, 0, valueBytes.length);
     291            byte[] result = new byte[_valueBytes.length];
     292            System.arraycopy(_valueBytes, 0, result, 0, _valueBytes.length);
    293293            return result;
    294294        }
     
    318318         */
    319319        public boolean isTypical() {
    320             if (tableClass == HuffmanTableClass.DC) {
     320            if (_tableClass == HuffmanTableClass.DC) {
    321321                return
    322                     Arrays.equals(lengthBytes, TYPICAL_LUMINANCE_DC_LENGTHS) &&
    323                     Arrays.equals(valueBytes, TYPICAL_LUMINANCE_DC_VALUES) ||
    324                     Arrays.equals(lengthBytes, TYPICAL_CHROMINANCE_DC_LENGTHS) &&
    325                     Arrays.equals(valueBytes, TYPICAL_CHROMINANCE_DC_VALUES);
    326             } else if (tableClass == HuffmanTableClass.AC) {
     322                    Arrays.equals(_lengthBytes, TYPICAL_LUMINANCE_DC_LENGTHS) &&
     323                    Arrays.equals(_valueBytes, TYPICAL_LUMINANCE_DC_VALUES) ||
     324                    Arrays.equals(_lengthBytes, TYPICAL_CHROMINANCE_DC_LENGTHS) &&
     325                    Arrays.equals(_valueBytes, TYPICAL_CHROMINANCE_DC_VALUES);
     326            } else if (_tableClass == HuffmanTableClass.AC) {
    327327                return
    328                     Arrays.equals(lengthBytes, TYPICAL_LUMINANCE_AC_LENGTHS) &&
    329                     Arrays.equals(valueBytes, TYPICAL_LUMINANCE_AC_VALUES) ||
    330                     Arrays.equals(lengthBytes, TYPICAL_CHROMINANCE_AC_LENGTHS) &&
    331                     Arrays.equals(valueBytes, TYPICAL_CHROMINANCE_AC_VALUES);
     328                    Arrays.equals(_lengthBytes, TYPICAL_LUMINANCE_AC_LENGTHS) &&
     329                    Arrays.equals(_valueBytes, TYPICAL_LUMINANCE_AC_VALUES) ||
     330                    Arrays.equals(_lengthBytes, TYPICAL_CHROMINANCE_AC_LENGTHS) &&
     331                    Arrays.equals(_valueBytes, TYPICAL_CHROMINANCE_AC_VALUES);
    332332            }
    333333            return false;
     
    351351            public static HuffmanTableClass typeOf(int value) {
    352352                switch (value) {
    353                     case 0: return DC;
    354                     case 1 : return AC;
    355                     default: return UNKNOWN;
     353                    case 0:
     354                        return DC;
     355                    case 1:
     356                        return AC;
     357                    default:
     358                        return UNKNOWN;
    356359                }
    357360            }
  • trunk/src/com/drew/metadata/jpeg/JpegCommentDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/jpeg/JpegCommentDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/jpeg/JpegCommentReader.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/jpeg/JpegComponent.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/jpeg/JpegDescriptor.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/jpeg/JpegDhtReader.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/jpeg/JpegDirectory.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/jpeg/JpegDnlReader.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3030
    3131import java.io.IOException;
    32 import java.util.Arrays;
    3332import java.util.Collections;
    3433
  • trunk/src/com/drew/metadata/jpeg/JpegReader.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/tiff/DirectoryTiffHandler.java

    r13061 r15217  
    11/*
    2  * Copyright 2002-2017 Drew Noakes
     2 * Copyright 2002-2019 Drew Noakes and contributors
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2424import com.drew.lang.Rational;
    2525import com.drew.lang.annotations.NotNull;
     26import com.drew.lang.annotations.Nullable;
    2627import com.drew.metadata.Directory;
    2728import com.drew.metadata.ErrorDirectory;
     
    4041    private final Stack<Directory> _directoryStack = new Stack<Directory>();
    4142
    42     protected Directory _currentDirectory;
     43    @Nullable private Directory _rootParentDirectory;
     44    @Nullable protected Directory _currentDirectory;
    4345    protected final Metadata _metadata;
    4446
    45     protected DirectoryTiffHandler(Metadata metadata)
     47    protected DirectoryTiffHandler(Metadata metadata, @Nullable Directory parentDirectory)
    4648    {
    4749        _metadata = metadata;
     50        _rootParentDirectory = parentDirectory;
    4851    }
    4952
     
    5558    protected void pushDirectory(@NotNull Class<? extends Directory> directoryClass)
    5659    {
    57         Directory newDirectory = null;
     60        Directory newDirectory;
    5861
    5962        try {
     
    6568        }
    6669
    67         if (newDirectory != null)
    68         {
    69             // If this is the first directory, don't add to the stack
    70             if (_currentDirectory != null)
    71             {
    72                 _directoryStack.push(_currentDirectory);
    73                 newDirectory.setParent(_currentDirectory);
     70        // If this is the first directory, don't add to the stack
     71        if (_currentDirectory == null) {
     72            // Apply any pending root parent to this new directory
     73            if (_rootParentDirectory != null) {
     74                newDirectory.setParent(_rootParentDirectory);
     75                _rootParentDirectory = null;
    7476            }
    75             _currentDirectory = newDirectory;
    76             _metadata.addDirectory(_currentDirectory);
    7777        }
     78        else {
     79            // The current directory is pushed onto the stack, and set as the new directory's parent
     80            _directoryStack.push(_currentDirectory);
     81            newDirectory.setParent(_currentDirectory);
     82        }
     83
     84        _currentDirectory = newDirectory;
     85        _metadata.addDirectory(_currentDirectory);
    7886    }
    7987
Note: See TracChangeset for help on using the changeset viewer.