Changeset 13061 in josm


Ignore:
Timestamp:
2017-10-30T22:46:09+01:00 (3 weeks ago)
Author:
Don-vip
Message:

fix #15505 - update to metadata-extractor 2.10.1

Location:
trunk/src/com/drew
Files:
52 added
12 deleted
98 edited

Legend:

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

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

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

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    4141//import com.drew.metadata.jfxx.JfxxReader;
    4242import com.drew.metadata.jpeg.JpegCommentReader;
     43import com.drew.metadata.jpeg.JpegDhtReader;
     44import com.drew.metadata.jpeg.JpegDnlReader;
    4345import com.drew.metadata.jpeg.JpegReader;
    4446//import com.drew.metadata.photoshop.DuckyReader;
     
    6365            //new PhotoshopReader(),
    6466            //new DuckyReader(),
    65             new IptcReader()//,
    66             //new AdobeJpegReader()
     67            new IptcReader(),
     68            //new AdobeJpegReader(),
     69            new JpegDhtReader(),
     70            new JpegDnlReader()
    6771    );
    6872
  • trunk/src/com/drew/imaging/jpeg/JpegProcessingException.java

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

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    5252     * @param segmentBytes the byte array holding data for the segment being added
    5353     */
     54    @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"})
    5455    public void addSegment(byte segmentType, @NotNull byte[] segmentBytes)
    5556    {
     
    206207     * @param occurrence  the zero-based index of the segment occurrence to remove.
    207208     */
     209    @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"})
    208210    public void removeSegmentOccurrence(@NotNull JpegSegmentType segmentType, int occurrence)
    209211    {
     
    218220     * @param occurrence  the zero-based index of the segment occurrence to remove.
    219221     */
     222    @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"})
    220223    public void removeSegmentOccurrence(byte segmentType, int occurrence)
    221224    {
  • trunk/src/com/drew/imaging/jpeg/JpegSegmentMetadataReader.java

    r8243 r13061  
    1313     */
    1414    @NotNull
    15     public Iterable<JpegSegmentType> getSegmentTypes();
     15    Iterable<JpegSegmentType> getSegmentTypes();
    1616
    1717    /**
     
    2323     * @param segmentType The {@link JpegSegmentType} being read.
    2424     */
    25     public void readJpegSegments(@NotNull final Iterable<byte[]> segments, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType);
     25    void readJpegSegments(@NotNull final Iterable<byte[]> segments, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType);
    2626}
  • trunk/src/com/drew/imaging/jpeg/JpegSegmentReader.java

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

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    9393    DQT((byte)0xDB, false),
    9494
     95    /** Define Number of Lines segment identifier. */
     96    DNL((byte)0xDC, false),
     97
     98    /** Define Restart Interval segment identifier. */
     99    DRI((byte)0xDD, false),
     100
     101    /** Define Hierarchical Progression segment identifier. */
     102    DHP((byte)0xDE, false),
     103
     104    /** EXPand reference component(s) segment identifier. */
     105    EXP((byte)0xDF, false),
     106
    95107    /** Define Huffman Table segment identifier. */
    96108    DHT((byte)0xC4, false),
    97109
    98     /** Start-of-Frame (0) segment identifier. */
     110    /** Define Arithmetic Coding conditioning segment identifier. */
     111    DAC((byte)0xCC, false),
     112
     113    /** Start-of-Frame (0) segment identifier for Baseline DCT. */
    99114    SOF0((byte)0xC0, true),
    100115
    101     /** Start-of-Frame (1) segment identifier. */
     116    /** Start-of-Frame (1) segment identifier for Extended sequential DCT. */
    102117    SOF1((byte)0xC1, true),
    103118
    104     /** Start-of-Frame (2) segment identifier. */
     119    /** Start-of-Frame (2) segment identifier for Progressive DCT. */
    105120    SOF2((byte)0xC2, true),
    106121
    107     /** Start-of-Frame (3) segment identifier. */
     122    /** Start-of-Frame (3) segment identifier for Lossless (sequential). */
    108123    SOF3((byte)0xC3, true),
    109124
     
    111126//    SOF4((byte)0xC4, true),
    112127
    113     /** Start-of-Frame (5) segment identifier. */
     128    /** Start-of-Frame (5) segment identifier for Differential sequential DCT. */
    114129    SOF5((byte)0xC5, true),
    115130
    116     /** Start-of-Frame (6) segment identifier. */
     131    /** Start-of-Frame (6) segment identifier for Differential progressive DCT. */
    117132    SOF6((byte)0xC6, true),
    118133
    119     /** Start-of-Frame (7) segment identifier. */
     134    /** Start-of-Frame (7) segment identifier for Differential lossless (sequential). */
    120135    SOF7((byte)0xC7, true),
    121136
    122     /** Start-of-Frame (8) segment identifier. */
    123     SOF8((byte)0xC8, true),
     137    /** Reserved for JPEG extensions. */
     138    JPG((byte)0xC8, true),
    124139
    125     /** Start-of-Frame (9) segment identifier. */
     140    /** Start-of-Frame (9) segment identifier for Extended sequential DCT. */
    126141    SOF9((byte)0xC9, true),
    127142
    128     /** Start-of-Frame (10) segment identifier. */
     143    /** Start-of-Frame (10) segment identifier for Progressive DCT. */
    129144    SOF10((byte)0xCA, true),
    130145
    131     /** Start-of-Frame (11) segment identifier. */
     146    /** Start-of-Frame (11) segment identifier for Lossless (sequential). */
    132147    SOF11((byte)0xCB, true),
    133148
     
    135150//    SOF12((byte)0xCC, true),
    136151
    137     /** Start-of-Frame (13) segment identifier. */
     152    /** Start-of-Frame (13) segment identifier for Differential sequential DCT. */
    138153    SOF13((byte)0xCD, true),
    139154
    140     /** Start-of-Frame (14) segment identifier. */
     155    /** Start-of-Frame (14) segment identifier for Differential progressive DCT. */
    141156    SOF14((byte)0xCE, true),
    142157
    143     /** Start-of-Frame (15) segment identifier. */
     158    /** Start-of-Frame (15) segment identifier for Differential lossless (sequential). */
    144159    SOF15((byte)0xCF, true),
    145160
    146     /** JPEG comment segment identifier. */
     161    /** JPEG comment segment identifier for comments. */
    147162    COM((byte)0xFE, true);
    148163
  • trunk/src/com/drew/imaging/tiff/TiffDataFormat.java

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

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2525import com.drew.lang.annotations.NotNull;
    2626import com.drew.lang.annotations.Nullable;
     27import com.drew.metadata.StringValue;
    2728
    2829import java.io.IOException;
     
    5253    void endingIFD();
    5354
    54     void completed(@NotNull final RandomAccessReader reader, final int tiffHeaderOffset);
    55 
    5655    @Nullable
    5756    Long tryCustomProcessFormat(int tagId, int formatCode, long componentCount);
     
    6867
    6968    void setByteArray(int tagId, @NotNull byte[] bytes);
    70     void setString(int tagId, @NotNull String string);
     69    void setString(int tagId, @NotNull StringValue string);
    7170    void setRational(int tagId, @NotNull Rational rational);
    7271    void setRationalArray(int tagId, @NotNull Rational[] array);
  • trunk/src/com/drew/imaging/tiff/TiffProcessingException.java

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

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    7777        Set<Integer> processedIfdOffsets = new HashSet<Integer>();
    7878        processIfd(handler, reader, processedIfdOffsets, firstIfdOffset, tiffHeaderOffset);
    79 
    80         handler.completed(reader, tiffHeaderOffset);
    8179    }
    8280
     
    265263                break;
    266264            case TiffDataFormat.CODE_STRING:
    267                 handler.setString(tagId, reader.getNullTerminatedString(tagValueOffset, componentCount));
     265                handler.setString(tagId, reader.getNullTerminatedStringValue(tagValueOffset, componentCount, null));
    268266                break;
    269267            case TiffDataFormat.CODE_RATIONAL_S:
  • trunk/src/com/drew/lang/BufferBoundsException.java

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

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2222package com.drew.lang;
    2323
     24import com.drew.lang.annotations.NotNull;
     25
    2426import java.io.IOException;
    25 
    26 import com.drew.lang.annotations.NotNull;
    2727
    2828/**
     
    3939    @NotNull
    4040    private final byte[] _buffer;
     41    private final int _baseOffset;
    4142
     43    @SuppressWarnings({ "ConstantConditions" })
     44    @com.drew.lang.annotations.SuppressWarnings(value = "EI_EXPOSE_REP2", justification = "Design intent")
    4245    public ByteArrayReader(@NotNull byte[] buffer)
     46    {
     47        this(buffer, 0);
     48    }
     49
     50    @SuppressWarnings({ "ConstantConditions" })
     51    @com.drew.lang.annotations.SuppressWarnings(value = "EI_EXPOSE_REP2", justification = "Design intent")
     52    public ByteArrayReader(@NotNull byte[] buffer, int baseOffset)
    4353    {
    4454        if (buffer == null)
    4555            throw new NullPointerException();
     56        if (baseOffset < 0)
     57            throw new IllegalArgumentException("Must be zero or greater");
    4658
    4759        _buffer = buffer;
     60        _baseOffset = baseOffset;
     61    }
     62
     63    @Override
     64    public int toUnshiftedOffset(int localOffset)
     65    {
     66        return localOffset + _baseOffset;
    4867    }
    4968
     
    5170    public long getLength()
    5271    {
    53         return _buffer.length;
     72        return _buffer.length - _baseOffset;
    5473    }
    5574
    5675    @Override
    57     protected byte getByte(int index) throws IOException
     76    public byte getByte(int index) throws IOException
    5877    {
    59         return _buffer[index];
     78        validateIndex(index, 1);
     79        return _buffer[index + _baseOffset];
    6080    }
    6181
     
    6484    {
    6585        if (!isValidIndex(index, bytesRequested))
    66             throw new BufferBoundsException(index, bytesRequested, _buffer.length);
     86            throw new BufferBoundsException(toUnshiftedOffset(index), bytesRequested, _buffer.length);
    6787    }
    6888
     
    7292        return bytesRequested >= 0
    7393            && index >= 0
    74             && (long)index + (long)bytesRequested - 1L < _buffer.length;
     94            && (long)index + (long)bytesRequested - 1L < getLength();
    7595    }
    7696
     
    82102
    83103        byte[] bytes = new byte[count];
    84         System.arraycopy(_buffer, index, bytes, 0, count);
     104        System.arraycopy(_buffer, index + _baseOffset, bytes, 0, count);
    85105        return bytes;
    86106    }
  • trunk/src/com/drew/lang/CompoundException.java

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

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

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2222package com.drew.lang;
    2323
    24 import com.drew.lang.annotations.NotNull;
    25 
    2624import java.io.IOException;
    2725import java.io.UnsupportedEncodingException;
     26import java.nio.charset.Charset;
     27
     28import com.drew.lang.annotations.NotNull;
     29import com.drew.lang.annotations.Nullable;
     30import com.drew.metadata.StringValue;
    2831
    2932/**
     
    3639 * <ul>
    3740 *     <li>{@link ByteArrayReader}</li>
    38  *     <li>{@link RandomAccessStreamReader}</li>
    3941 * </ul>
    4042 *
     
    4547    private boolean _isMotorolaByteOrder = true;
    4648
     49    public abstract int toUnshiftedOffset(int localOffset);
     50
    4751    /**
    4852     * Gets the byte value at the specified byte <code>index</code>.
     
    5761     * @throws IOException if the byte is unable to be read
    5862     */
    59     protected abstract byte getByte(int index) throws IOException;
     63    public abstract byte getByte(int index) throws IOException;
    6064
    6165    /**
     
    8993     * Returns the length of the data source in bytes.
    9094     * <p>
    91      * This is a simple operation for implementations (such as {@link RandomAccessFileReader} and
     95     * This is a simple operation for implementations (such as
    9296     * {@link ByteArrayReader}) that have the entire data source available.
    9397     * <p>
    94      * Users of this method must be aware that sequentially accessed implementations such as
    95      * {@link RandomAccessStreamReader} will have to read and buffer the entire data source in
     98     * Users of this method must be aware that sequentially accessed implementations
     99     * will have to read and buffer the entire data source in
    96100     * order to determine the length.
    97101     *
     
    207211        if (_isMotorolaByteOrder) {
    208212            // Motorola - MSB first
    209             return (short) (((short)getByte(index    ) << 8 & (short)0xFF00) |
    210                             ((short)getByte(index + 1)      & (short)0xFF));
     213            return (short) ((getByte(index    ) << 8 & (short)0xFF00) |
     214                            (getByte(index + 1)      & (short)0xFF));
    211215        } else {
    212216            // Intel ordering - LSB first
    213             return (short) (((short)getByte(index + 1) << 8 & (short)0xFF00) |
    214                             ((short)getByte(index    )      & (short)0xFF));
     217            return (short) ((getByte(index + 1) << 8 & (short)0xFF00) |
     218                            (getByte(index    )      & (short)0xFF));
    215219        }
    216220    }
     
    229233        if (_isMotorolaByteOrder) {
    230234            // Motorola - MSB first (big endian)
    231             return (((int)getByte(index    )) << 16 & 0xFF0000) |
    232                    (((int)getByte(index + 1)) << 8  & 0xFF00) |
    233                    (((int)getByte(index + 2))       & 0xFF);
     235            return ((getByte(index    )) << 16 & 0xFF0000) |
     236                   ((getByte(index + 1)) << 8  & 0xFF00) |
     237                   ((getByte(index + 2))       & 0xFF);
    234238        } else {
    235239            // Intel ordering - LSB first (little endian)
    236             return (((int)getByte(index + 2)) << 16 & 0xFF0000) |
    237                    (((int)getByte(index + 1)) << 8  & 0xFF00) |
    238                    (((int)getByte(index    ))       & 0xFF);
     240            return ((getByte(index + 2)) << 16 & 0xFF0000) |
     241                   ((getByte(index + 1)) << 8  & 0xFF00) |
     242                   ((getByte(index    ))       & 0xFF);
    239243        }
    240244    }
     
    256260                   (((long)getByte(index + 1)) << 16 & 0xFF0000L) |
    257261                   (((long)getByte(index + 2)) << 8  & 0xFF00L) |
    258                    (((long)getByte(index + 3))       & 0xFFL);
     262                   ((getByte(index + 3))       & 0xFFL);
    259263        } else {
    260264            // Intel ordering - LSB first (little endian)
     
    262266                   (((long)getByte(index + 2)) << 16 & 0xFF0000L) |
    263267                   (((long)getByte(index + 1)) << 8  & 0xFF00L) |
    264                    (((long)getByte(index    ))       & 0xFFL);
     268                   ((getByte(index    ))       & 0xFFL);
    265269        }
    266270    }
     
    312316                   ((long)getByte(index + 5) << 16 & 0xFF0000L) |
    313317                   ((long)getByte(index + 6) << 8  & 0xFF00L) |
    314                    ((long)getByte(index + 7)       & 0xFFL);
     318                   (getByte(index + 7)       & 0xFFL);
    315319        } else {
    316320            // Intel ordering - LSB first
     
    322326                   ((long)getByte(index + 2) << 16 & 0xFF0000L) |
    323327                   ((long)getByte(index + 1) << 8  & 0xFF00L) |
    324                    ((long)getByte(index    )       & 0xFFL);
     328                   (getByte(index    )       & 0xFFL);
    325329        }
    326330    }
     
    365369
    366370    @NotNull
    367     public String getString(int index, int bytesRequested) throws IOException
    368     {
    369         return new String(getBytes(index, bytesRequested));
    370     }
    371 
    372     @NotNull
    373     public String getString(int index, int bytesRequested, String charset) throws IOException
     371    public StringValue getStringValue(int index, int bytesRequested, @Nullable Charset charset) throws IOException
     372    {
     373        return new StringValue(getBytes(index, bytesRequested), charset);
     374    }
     375
     376    @NotNull
     377    public String getString(int index, int bytesRequested, @NotNull Charset charset) throws IOException
     378    {
     379        return new String(getBytes(index, bytesRequested), charset.name());
     380    }
     381
     382    @NotNull
     383    public String getString(int index, int bytesRequested, @NotNull String charset) throws IOException
    374384    {
    375385        byte[] bytes = getBytes(index, bytesRequested);
     
    392402     */
    393403    @NotNull
    394     public String getNullTerminatedString(int index, int maxLengthBytes) throws IOException
    395     {
    396         // NOTE currently only really suited to single-byte character strings
    397 
    398         byte[] bytes = getBytes(index, maxLengthBytes);
     404    public String getNullTerminatedString(int index, int maxLengthBytes, @NotNull Charset charset) throws IOException
     405    {
     406        return new String(getNullTerminatedBytes(index, maxLengthBytes), charset.name());
     407    }
     408
     409    @NotNull
     410    public StringValue getNullTerminatedStringValue(int index, int maxLengthBytes, @Nullable Charset charset) throws IOException
     411    {
     412        byte[] bytes = getNullTerminatedBytes(index, maxLengthBytes);
     413
     414        return new StringValue(bytes, charset);
     415    }
     416
     417    /**
     418     * Returns the sequence of bytes punctuated by a <code>\0</code> value.
     419     *
     420     * @param index The index within the buffer at which to start reading the string.
     421     * @param maxLengthBytes The maximum number of bytes to read. If a <code>\0</code> byte is not reached within this limit,
     422     * the returned array will be <code>maxLengthBytes</code> long.
     423     * @return The read byte array, excluding the null terminator.
     424     * @throws IOException The buffer does not contain enough bytes to satisfy this request.
     425     */
     426    @NotNull
     427    public byte[] getNullTerminatedBytes(int index, int maxLengthBytes) throws IOException
     428    {
     429        byte[] buffer = getBytes(index, maxLengthBytes);
    399430
    400431        // Count the number of non-null bytes
    401432        int length = 0;
    402         while (length < bytes.length && bytes[length] != '\0')
     433        while (length < buffer.length && buffer[length] != 0)
    403434            length++;
    404435
    405         return new String(bytes, 0, length);
     436        if (length == maxLengthBytes)
     437            return buffer;
     438
     439        byte[] bytes = new byte[length];
     440        if (length > 0)
     441            System.arraycopy(buffer, 0, bytes, 0, length);
     442        return bytes;
    406443    }
    407444}
  • trunk/src/com/drew/lang/Rational.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3636 * @author Drew Noakes https://drewnoakes.com
    3737 */
    38 public class Rational extends java.lang.Number implements Serializable
     38@SuppressWarnings("WeakerAccess")
     39public class Rational extends java.lang.Number implements Comparable<Rational>, Serializable
    3940{
    4041    private static final long serialVersionUID = 510688928138848770L;
     
    173174                (_denominator != 0 && (_numerator % _denominator == 0)) ||
    174175                (_denominator == 0 && _numerator == 0);
     176    }
     177
     178    /** Checks if either the numerator or denominator are zero. */
     179    public boolean isZero()
     180    {
     181        return _numerator == 0 || _denominator == 0;
    175182    }
    176183
     
    212219
    213220    /**
    214      * Decides whether a brute-force simplification calculation should be avoided
    215      * by comparing the maximum number of possible calculations with some threshold.
    216      *
    217      * @return true if the simplification should be performed, otherwise false
    218      */
    219     private boolean tooComplexForSimplification()
    220     {
    221         double maxPossibleCalculations = (((double) (Math.min(_denominator, _numerator) - 1) / 5d) + 2);
    222         final int maxSimplificationCalculations = 1000;
    223         return maxPossibleCalculations > maxSimplificationCalculations;
     221     * Compares two {@link Rational} instances, returning true if they are mathematically
     222     * equivalent (in consistence with {@link Rational#equals(Object)} method).
     223     *
     224     * @param that the {@link Rational} to compare this instance to.
     225     * @return the value {@code 0} if this {@link Rational} is
     226     *         equal to the argument {@link Rational} mathematically; a value less
     227     *         than {@code 0} if this {@link Rational} is less
     228     *         than the argument {@link Rational}; and a value greater
     229     *         than {@code 0} if this {@link Rational} is greater than the argument
     230     *         {@link Rational}.
     231     */
     232    public int compareTo(@NotNull Rational that) {
     233        return Double.compare(this.doubleValue(), that.doubleValue());
     234    }
     235
     236    /**
     237     * Indicates whether this instance and <code>other</code> are numerically equal,
     238     * even if their representations differ.
     239     *
     240     * For example, 1/2 is equal to 10/20 by this method.
     241     * Similarly, 1/0 is equal to 100/0 by this method.
     242     * To test equal representations, use EqualsExact.
     243     *
     244     * @param other The rational value to compare with
     245     */
     246    public boolean equals(Rational other) {
     247        return other.doubleValue() == doubleValue();
     248    }
     249
     250    /**
     251     * Indicates whether this instance and <code>other</code> have identical
     252     * Numerator and Denominator.
     253     * <p>
     254     * For example, 1/2 is not equal to 10/20 by this method.
     255     * Similarly, 1/0 is not equal to 100/0 by this method.
     256     * To test numerically equivalence, use Equals(Rational).</p>
     257     *
     258     * @param other The rational value to compare with
     259     */
     260    public boolean equalsExact(Rational other) {
     261        return getDenominator() == other.getDenominator() && getNumerator() == other.getNumerator();
    224262    }
    225263
     
    249287    /**
    250288     * <p>
    251      * Simplifies the {@link Rational} number.</p>
     289     * Simplifies the representation of this {@link Rational} number.</p>
    252290     * <p>
    253      * Prime number series: 1, 2, 3, 5, 7, 9, 11, 13, 17</p>
     291     * For example, 5/10 simplifies to 1/2 because both Numerator
     292     * and Denominator share a common factor of 5.</p>
    254293     * <p>
    255      * To reduce a rational, need to see if both numerator and denominator are divisible
    256      * by a common factor.  Using the prime number series in ascending order guarantees
    257      * the minimum number of checks required.</p>
    258      * <p>
    259      * However, generating the prime number series seems to be a hefty task.  Perhaps
    260      * it's simpler to check if both d &amp; n are divisible by all numbers from 2 {@literal ->}
    261      * (Math.min(denominator, numerator) / 2).  In doing this, one can check for 2
    262      * and 5 once, then ignore all even numbers, and all numbers ending in 0 or 5.
    263      * This leaves four numbers from every ten to check.</p>
    264      * <p>
    265      * Therefore, the max number of pairs of modulus divisions required will be:</p>
    266      * <pre><code>
    267      *    4   Math.min(denominator, numerator) - 1
    268      *   -- * ------------------------------------ + 2
    269      *   10                    2
    270      *
    271      *   Math.min(denominator, numerator) - 1
    272      * = ------------------------------------ + 2
    273      *                  5
    274      * </code></pre>
    275      *
    276      * @return a simplified instance, or if the Rational could not be simplified,
    277      *         returns itself (unchanged)
     294     * Uses the Euclidean Algorithm to find the greatest common divisor.</p>
     295     *
     296     * @return A simplified instance if one exists, otherwise a copy of the original value.
    278297     */
    279298    @NotNull
    280299    public Rational getSimplifiedInstance()
    281300    {
    282         if (tooComplexForSimplification()) {
    283             return this;
     301        long gcd = GCD(_numerator, _denominator);
     302
     303        return new Rational(_numerator / gcd, _denominator / gcd);
     304    }
     305
     306    private static long GCD(long a, long b)
     307    {
     308        if (a < 0)
     309            a = -a;
     310        if (b < 0)
     311            b = -b;
     312
     313        while (a != 0 && b != 0)
     314        {
     315            if (a > b)
     316                a %= b;
     317            else
     318                b %= a;
    284319        }
    285         for (int factor = 2; factor <= Math.min(_denominator, _numerator); factor++) {
    286             if ((factor % 2 == 0 && factor > 2) || (factor % 5 == 0 && factor > 5)) {
    287                 continue;
    288             }
    289             if (_denominator % factor == 0 && _numerator % factor == 0) {
    290                 // found a common factor
    291                 return new Rational(_numerator / factor, _denominator / factor);
    292             }
    293         }
    294         return this;
     320
     321        return a == 0 ? b : a;
    295322    }
    296323}
  • trunk/src/com/drew/lang/SequentialByteArrayReader.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3737    private int _index;
    3838
     39    @Override
     40    public long getPosition()
     41    {
     42        return _index;
     43    }
     44
    3945    public SequentialByteArrayReader(@NotNull byte[] bytes)
    4046    {
     
    4248    }
    4349
     50    @SuppressWarnings("ConstantConditions")
    4451    public SequentialByteArrayReader(@NotNull byte[] bytes, int baseIndex)
    4552    {
     
    5259
    5360    @Override
    54     protected byte getByte() throws IOException
     61    public byte getByte() throws IOException
    5562    {
    5663        if (_index >= _bytes.length) {
     
    7380
    7481        return bytes;
     82    }
     83
     84    @Override
     85    public void getBytes(@NotNull byte[] buffer, int offset, int count) throws IOException
     86    {
     87        if (_index + count > _bytes.length) {
     88            throw new EOFException("End of data reached.");
     89        }
     90
     91        System.arraycopy(_bytes, _index, buffer, offset, count);
     92        _index += count;
    7593    }
    7694
     
    105123        return true;
    106124    }
     125
     126    @Override
     127    public int available() {
     128        return _bytes.length - _index;
     129    }
    107130}
  • trunk/src/com/drew/lang/SequentialReader.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2323
    2424import com.drew.lang.annotations.NotNull;
     25import com.drew.lang.annotations.Nullable;
     26import com.drew.metadata.StringValue;
    2527
    2628import java.io.EOFException;
    2729import java.io.IOException;
    2830import java.io.UnsupportedEncodingException;
     31import java.nio.charset.Charset;
    2932
    3033/**
    3134 * @author Drew Noakes https://drewnoakes.com
    3235 */
     36@SuppressWarnings("WeakerAccess")
    3337public abstract class SequentialReader
    3438{
     
    3741    private boolean _isMotorolaByteOrder = true;
    3842
     43    public abstract long getPosition() throws IOException;
     44
    3945    /**
    4046     * Gets the next byte in the sequence.
     
    4248     * @return The read byte value
    4349     */
    44     protected abstract byte getByte() throws IOException;
     50    public abstract byte getByte() throws IOException;
    4551
    4652    /**
     
    5258    @NotNull
    5359    public abstract byte[] getBytes(int count) throws IOException;
     60
     61    /**
     62     * Retrieves bytes, writing them into a caller-provided buffer.
     63     * @param buffer The array to write bytes to.
     64     * @param offset The starting position within buffer to write to.
     65     * @param count The number of bytes to be written.
     66     */
     67    public abstract void getBytes(@NotNull byte[] buffer, int offset, int count) throws IOException;
    5468
    5569    /**
     
    7084     */
    7185    public abstract boolean trySkip(long n) throws IOException;
     86
     87    /**
     88     * Returns an estimate of the number of bytes that can be read (or skipped
     89     * over) from this {@link SequentialReader} without blocking by the next
     90     * invocation of a method for this input stream. A single read or skip of
     91     * this many bytes will not block, but may read or skip fewer bytes.
     92     * <p>
     93     * Note that while some implementations of {@link SequentialReader} like
     94     * {@link SequentialByteArrayReader} will return the total remaining number
     95     * of bytes in the stream, others will not. It is never correct to use the
     96     * return value of this method to allocate a buffer intended to hold all
     97     * data in this stream.
     98     *
     99     * @return an estimate of the number of bytes that can be read (or skipped
     100     *         over) from this {@link SequentialReader} without blocking or
     101     *         {@code 0} when it reaches the end of the input stream.
     102     */
     103    public abstract int available();
    72104
    73105    /**
     
    284316    }
    285317
     318    @NotNull
     319    public String getString(int bytesRequested, @NotNull Charset charset) throws IOException
     320    {
     321        byte[] bytes = getBytes(bytesRequested);
     322        return new String(bytes, charset);
     323    }
     324
     325    @NotNull
     326    public StringValue getStringValue(int bytesRequested, @Nullable Charset charset) throws IOException
     327    {
     328        return new StringValue(getBytes(bytesRequested), charset);
     329    }
     330
    286331    /**
    287332     * Creates a String from the stream, ending where <code>byte=='\0'</code> or where <code>length==maxLength</code>.
     
    293338     */
    294339    @NotNull
    295     public String getNullTerminatedString(int maxLengthBytes) throws IOException
    296     {
    297         // NOTE currently only really suited to single-byte character strings
    298 
    299         byte[] bytes = new byte[maxLengthBytes];
     340    public String getNullTerminatedString(int maxLengthBytes, Charset charset) throws IOException
     341    {
     342       return getNullTerminatedStringValue(maxLengthBytes, charset).toString();
     343    }
     344
     345    /**
     346     * Creates a String from the stream, ending where <code>byte=='\0'</code> or where <code>length==maxLength</code>.
     347     *
     348     * @param maxLengthBytes The maximum number of bytes to read.  If a <code>\0</code> byte is not reached within this limit,
     349     *                       reading will stop and the string will be truncated to this length.
     350     * @param charset The <code>Charset</code> to register with the returned <code>StringValue</code>, or <code>null</code> if the encoding
     351     *                is unknown
     352     * @return The read string.
     353     * @throws IOException The buffer does not contain enough bytes to satisfy this request.
     354     */
     355    @NotNull
     356    public StringValue getNullTerminatedStringValue(int maxLengthBytes, Charset charset) throws IOException
     357    {
     358        byte[] bytes = getNullTerminatedBytes(maxLengthBytes);
     359
     360        return new StringValue(bytes, charset);
     361    }
     362
     363    /**
     364     * Returns the sequence of bytes punctuated by a <code>\0</code> value.
     365     *
     366     * @param maxLengthBytes The maximum number of bytes to read. If a <code>\0</code> byte is not reached within this limit,
     367     * the returned array will be <code>maxLengthBytes</code> long.
     368     * @return The read byte array, excluding the null terminator.
     369     * @throws IOException The buffer does not contain enough bytes to satisfy this request.
     370     */
     371    @NotNull
     372    public byte[] getNullTerminatedBytes(int maxLengthBytes) throws IOException
     373    {
     374        byte[] buffer = new byte[maxLengthBytes];
    300375
    301376        // Count the number of non-null bytes
    302377        int length = 0;
    303         while (length < bytes.length && (bytes[length] = getByte()) != '\0')
     378        while (length < buffer.length && (buffer[length] = getByte()) != 0)
    304379            length++;
    305380
    306         return new String(bytes, 0, length);
     381        if (length == maxLengthBytes)
     382            return buffer;
     383
     384        byte[] bytes = new byte[length];
     385        if (length > 0)
     386            System.arraycopy(buffer, 0, bytes, 0, length);
     387        return bytes;
    307388    }
    308389}
  • trunk/src/com/drew/lang/StreamReader.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3737    private final InputStream _stream;
    3838
     39    private long _pos;
     40
     41    @Override
     42    public long getPosition()
     43    {
     44        return _pos;
     45    }
     46
     47    @SuppressWarnings("ConstantConditions")
    3948    public StreamReader(@NotNull InputStream stream)
    4049    {
     
    4352
    4453        _stream = stream;
     54        _pos = 0;
    4555    }
    4656
    4757    @Override
    48     protected byte getByte() throws IOException
     58    public byte getByte() throws IOException
    4959    {
    5060        int value = _stream.read();
    5161        if (value == -1)
    5262            throw new EOFException("End of data reached.");
     63        _pos++;
    5364        return (byte)value;
    5465    }
     
    5970    {
    6071        byte[] bytes = new byte[count];
     72        getBytes(bytes, 0, count);
     73        return bytes;
     74    }
     75
     76    @Override
     77    public void getBytes(@NotNull byte[] buffer, int offset, int count) throws IOException
     78    {
    6179        int totalBytesRead = 0;
    62 
    63         while (totalBytesRead != count) {
    64             final int bytesRead = _stream.read(bytes, totalBytesRead, count - totalBytesRead);
     80        while (totalBytesRead != count)
     81        {
     82            final int bytesRead = _stream.read(buffer, offset + totalBytesRead, count - totalBytesRead);
    6583            if (bytesRead == -1)
    6684                throw new EOFException("End of data reached.");
     
    6886            assert(totalBytesRead <= count);
    6987        }
    70 
    71         return bytes;
     88        _pos += totalBytesRead;
    7289    }
    7390
     
    93110    }
    94111
     112    @Override
     113    public int available() {
     114        try {
     115            return _stream.available();
     116        } catch (IOException e) {
     117            return 0;
     118        }
     119    }
     120
    95121    private long skipInternal(long n) throws IOException
    96122    {
     
    109135                break;
    110136        }
     137        _pos += skippedTotal;
    111138        return skippedTotal;
    112139    }
  • trunk/src/com/drew/lang/StringUtil.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3434 * @author Drew Noakes https://drewnoakes.com
    3535 */
    36 public class StringUtil
     36public final class StringUtil
    3737{
    3838    @NotNull
  • trunk/src/com/drew/lang/annotations/NotNull.java

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

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

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    5151    public static Age fromPanasonicString(@NotNull String s)
    5252    {
    53         if (s == null)
    54             throw new NullPointerException();
    55 
    5653        if (s.length() != 19 || s.startsWith("9999:99:99"))
    5754            return null;
     
    143140
    144141    @Override
    145     public boolean equals(Object o)
     142    public boolean equals(@Nullable Object o)
    146143    {
    147144        if (this == o) return true;
  • trunk/src/com/drew/metadata/Directory.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2424import com.drew.lang.annotations.NotNull;
    2525import com.drew.lang.annotations.Nullable;
     26import com.drew.lang.annotations.SuppressWarnings;
    2627
    2728import java.io.UnsupportedEncodingException;
     
    4142 * @author Drew Noakes https://drewnoakes.com
    4243 */
     44@java.lang.SuppressWarnings("WeakerAccess")
    4345public abstract class Directory
    4446{
    45     private static final DecimalFormat _floatFormat = new DecimalFormat("0.###");
     47    private static final String _floatFormatPattern = "0.###";
    4648
    4749    /** Map of values hashed by type identifiers. */
     
    260262
    261263    /**
     264     * Sets a <code>StringValue</code> value for the specified tag.
     265     *
     266     * @param tagType the tag's value as an int
     267     * @param value   the value for the specified tag as a StringValue
     268     */
     269    @java.lang.SuppressWarnings({ "ConstantConditions" })
     270    public void setStringValue(int tagType, @NotNull StringValue value)
     271    {
     272        if (value == null)
     273            throw new NullPointerException("cannot set a null StringValue");
     274        setObject(tagType, value);
     275    }
     276
     277    /**
    262278     * Sets a <code>String</code> value for the specified tag.
    263279     *
     
    280296     */
    281297    public void setStringArray(int tagType, @NotNull String[] strings)
     298    {
     299        setObjectArray(tagType, strings);
     300    }
     301
     302    /**
     303     * Sets a <code>StringValue[]</code> (array) for the specified tag.
     304     *
     305     * @param tagType the tag identifier
     306     * @param strings the StringValue array to store
     307     */
     308    public void setStringValueArray(int tagType, @NotNull StringValue[] strings)
    282309    {
    283310        setObjectArray(tagType, strings);
     
    440467        if (o instanceof Number) {
    441468            return ((Number)o).intValue();
    442         } else if (o instanceof String) {
     469        } else if (o instanceof String || o instanceof StringValue) {
    443470            try {
    444                 return Integer.parseInt((String)o);
     471                return Integer.parseInt(o.toString());
    445472            } catch (NumberFormatException nfe) {
    446473                // convert the char array to an int
    447                 String s = (String)o;
     474                String s = o.toString();
    448475                byte[] bytes = s.getBytes();
    449476                long val = 0;
     
    466493            if (ints.length == 1)
    467494                return ints[0];
     495        } else if (o instanceof short[]) {
     496            short[] shorts = (short[])o;
     497            if (shorts.length == 1)
     498                return (int)shorts[0];
    468499        }
    469500        return null;
     
    472503    /**
    473504     * Gets the specified tag's value as a String array, if possible.  Only supported
    474      * where the tag is set as String[], String, int[], byte[] or Rational[].
     505     * where the tag is set as StringValue[], String[], StringValue, String, int[], byte[] or Rational[].
    475506     *
    476507     * @param tagType the tag identifier
     
    487518        if (o instanceof String)
    488519            return new String[] { (String)o };
     520        if (o instanceof StringValue)
     521            return new String[] { o.toString() };
     522        if (o instanceof StringValue[]) {
     523            StringValue[] stringValues = (StringValue[])o;
     524            String[] strings = new String[stringValues.length];
     525            for (int i = 0; i < strings.length; i++)
     526                strings[i] = stringValues[i].toString();
     527            return strings;
     528        }
    489529        if (o instanceof int[]) {
    490530            int[] ints = (int[])o;
     
    493533                strings[i] = Integer.toString(ints[i]);
    494534            return strings;
    495         } else if (o instanceof byte[]) {
     535        }
     536        if (o instanceof byte[]) {
    496537            byte[] bytes = (byte[])o;
    497538            String[] strings = new String[bytes.length];
     
    499540                strings[i] = Byte.toString(bytes[i]);
    500541            return strings;
    501         } else if (o instanceof Rational[]) {
     542        }
     543        if (o instanceof Rational[]) {
    502544            Rational[] rationals = (Rational[])o;
    503545            String[] strings = new String[rationals.length];
     
    506548            return strings;
    507549        }
     550        return null;
     551    }
     552
     553    /**
     554     * Gets the specified tag's value as a StringValue array, if possible.
     555     * Only succeeds if the tag is set as StringValue[], or StringValue.
     556     *
     557     * @param tagType the tag identifier
     558     * @return the tag's value as an array of StringValues. If the value is unset or cannot be converted, <code>null</code> is returned.
     559     */
     560    @Nullable
     561    public StringValue[] getStringValueArray(int tagType)
     562    {
     563        Object o = getObject(tagType);
     564        if (o == null)
     565            return null;
     566        if (o instanceof StringValue[])
     567            return (StringValue[])o;
     568        if (o instanceof StringValue)
     569            return new StringValue[] {(StringValue) o};
    508570        return null;
    509571    }
     
    575637        if (o == null) {
    576638            return null;
     639        } else if (o instanceof StringValue) {
     640            return ((StringValue)o).getBytes();
    577641        } else if (o instanceof Rational[]) {
    578642            Rational[] rationals = (Rational[])o;
     
    630694        if (o == null)
    631695            return null;
    632         if (o instanceof String) {
     696        if (o instanceof String || o instanceof StringValue) {
    633697            try {
    634                 return Double.parseDouble((String)o);
     698                return Double.parseDouble(o.toString());
    635699            } catch (NumberFormatException nfe) {
    636700                return null;
     
    662726        if (o == null)
    663727            return null;
    664         if (o instanceof String) {
     728        if (o instanceof String || o instanceof StringValue) {
    665729            try {
    666                 return Float.parseFloat((String)o);
     730                return Float.parseFloat(o.toString());
    667731            } catch (NumberFormatException nfe) {
    668732                return null;
     
    678742    {
    679743        Long value = getLongObject(tagType);
    680         if (value!=null)
     744        if (value != null)
    681745            return value;
    682746        Object o = getObject(tagType);
     
    693757        if (o == null)
    694758            return null;
    695         if (o instanceof String) {
     759        if (o instanceof String || o instanceof StringValue) {
    696760            try {
    697                 return Long.parseLong((String)o);
     761                return Long.parseLong(o.toString());
    698762            } catch (NumberFormatException nfe) {
    699763                return null;
     
    709773    {
    710774        Boolean value = getBooleanObject(tagType);
    711         if (value!=null)
     775        if (value != null)
    712776            return value;
    713777        Object o = getObject(tagType);
     
    719783    /** Returns the specified tag's value as a boolean.  If the tag is not set or cannot be converted, <code>null</code> is returned. */
    720784    @Nullable
     785    @SuppressWarnings(value = "NP_BOOLEAN_RETURN_NULL", justification = "keep API interface consistent")
    721786    public Boolean getBooleanObject(int tagType)
    722787    {
     
    726791        if (o instanceof Boolean)
    727792            return (Boolean)o;
    728         if (o instanceof String) {
     793        if (o instanceof String || o instanceof StringValue) {
    729794            try {
    730                 return Boolean.getBoolean((String)o);
     795                return Boolean.getBoolean(o.toString());
    731796            } catch (NumberFormatException nfe) {
    732797                return null;
     
    788853        java.util.Date date = null;
    789854
    790         if (o instanceof String) {
     855        if ((o instanceof String) || (o instanceof StringValue)) {
    791856            // This seems to cover all known Exif and Xmp date strings
    792857            // Note that "    :  :     :  :  " is a valid date string according to the Exif spec (which means 'unknown date'): http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif/datetimeoriginal.html
     
    802867                    "yyyy-MM-dd",
    803868                    "yyyy-MM",
     869                    "yyyyMMdd", // as used in IPTC data
    804870                    "yyyy" };
    805             String dateString = (String)o;
     871
     872            String dateString = o.toString();
    806873
    807874            // if the date string has subsecond information, it supersedes the subsecond parameter
     
    9451012                    if (i != 0)
    9461013                        string.append(' ');
    947                     string.append(_floatFormat.format(Array.getFloat(o, i)));
     1014                    string.append(new DecimalFormat(_floatFormatPattern).format(Array.getFloat(o, i)));
    9481015                }
    9491016            } else if (componentType.getName().equals("double")) {
     
    9511018                    if (i != 0)
    9521019                        string.append(' ');
    953                     string.append(_floatFormat.format(Array.getDouble(o, i)));
     1020                    string.append(new DecimalFormat(_floatFormatPattern).format(Array.getDouble(o, i)));
    9541021                }
    9551022            } else if (componentType.getName().equals("byte")) {
     
    9671034
    9681035        if (o instanceof Double)
    969             return _floatFormat.format(((Double)o).doubleValue());
     1036            return new DecimalFormat(_floatFormatPattern).format(((Double)o).doubleValue());
    9701037
    9711038        if (o instanceof Float)
    972             return _floatFormat.format(((Float)o).floatValue());
     1039            return new DecimalFormat(_floatFormatPattern).format(((Float)o).floatValue());
    9731040
    9741041        // Note that several cameras leave trailing spaces (Olympus, Nikon) but this library is intended to show
     
    9901057            return null;
    9911058        }
     1059    }
     1060
     1061    @Nullable
     1062    public StringValue getStringValue(int tagType)
     1063    {
     1064        Object o = getObject(tagType);
     1065        if (o instanceof StringValue)
     1066            return (StringValue)o;
     1067        return null;
    9921068    }
    9931069
  • trunk/src/com/drew/metadata/Face.java

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

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    5353    }
    5454
    55     @Nullable
     55    @NotNull
    5656    @SuppressWarnings("unchecked")
    5757    public <T extends Directory> Collection<T> getDirectoriesOfType(Class<T> type)
  • trunk/src/com/drew/metadata/MetadataException.java

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

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3030 * @author Drew Noakes https://drewnoakes.com
    3131 */
     32@SuppressWarnings("unused")
    3233public class Tag
    3334{
     
    8485     * @return whether this tag has a name
    8586     */
    86     @NotNull
    8787    public boolean hasTagName()
    8888    {
  • trunk/src/com/drew/metadata/TagDescriptor.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2929import java.lang.reflect.Array;
    3030import java.math.RoundingMode;
     31import java.nio.charset.Charset;
    3132import java.text.DecimalFormat;
    3233import java.text.SimpleDateFormat;
     
    7475            final int length = Array.getLength(object);
    7576            if (length > 16) {
    76                 return String.format("[%d %s]", length, length == 1 ? "value" : "values");
     77                return String.format("[%d values]", length);
    7778            }
    7879        }
     
    269270
    270271    @Nullable
    271     protected String getAsciiStringFromBytes(int tag)
     272    protected String getStringFromBytes(int tag, Charset cs)
    272273    {
    273274        byte[] values = _directory.getByteArray(tag);
     
    277278
    278279        try {
    279             return new String(values, "ASCII").trim();
     280            return new String(values, cs.name()).trim();
    280281        } catch (UnsupportedEncodingException e) {
    281282            return null;
     
    321322        Rational[] values = _directory.getRationalArray(tag);
    322323
    323         if (values == null || values.length != 4 || (values[0].doubleValue() == 0 && values[2].doubleValue() == 0))
     324        if (values == null || values.length != 4 || (values[0].isZero() && values[2].isZero()))
    324325            return null;
    325326
     
    331332            sb.append(values[0].toSimpleString(true)).append('-').append(values[1].toSimpleString(true)).append("mm");
    332333
    333         if (values[2].doubleValue() != 0) {
     334        if (!values[2].isZero()) {
    334335            sb.append(' ');
    335336
     
    345346        return sb.toString();
    346347    }
     348
     349    @Nullable
     350    protected String getOrientationDescription(int tag)
     351    {
     352        return getIndexedDescription(tag, 1,
     353            "Top, left side (Horizontal / normal)",
     354            "Top, right side (Mirror horizontal)",
     355            "Bottom, right side (Rotate 180)",
     356            "Bottom, left side (Mirror vertical)",
     357            "Left side, top (Mirror horizontal and rotate 270 CW)",
     358            "Right side, top (Rotate 90 CW)",
     359            "Right side, bottom (Mirror horizontal and rotate 90 CW)",
     360            "Left side, bottom (Rotate 270 CW)");
     361    }
     362
     363    @Nullable
     364    protected String getShutterSpeedDescription(int tag)
     365    {
     366        // I believe this method to now be stable, but am leaving some alternative snippets of
     367        // code in here, to assist anyone who's looking into this (given that I don't have a public CVS).
     368
     369//        float apexValue = _directory.getFloat(ExifSubIFDDirectory.TAG_SHUTTER_SPEED);
     370//        int apexPower = (int)Math.pow(2.0, apexValue);
     371//        return "1/" + apexPower + " sec";
     372        // TODO test this method
     373        // thanks to Mark Edwards for spotting and patching a bug in the calculation of this
     374        // description (spotted bug using a Canon EOS 300D)
     375        // thanks also to Gli Blr for spotting this bug
     376        Float apexValue = _directory.getFloatObject(tag);
     377        if (apexValue == null)
     378            return null;
     379        if (apexValue <= 1) {
     380            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;
     383            DecimalFormat format = new DecimalFormat("0.##");
     384            format.setRoundingMode(RoundingMode.HALF_UP);
     385            return format.format(fApexPower) + " sec";
     386        } else {
     387            int apexPower = (int)((Math.exp(apexValue * Math.log(2))));
     388            return "1/" + apexPower + " sec";
     389        }
     390
     391/*
     392        // This alternative implementation offered by Bill Richards
     393        // TODO determine which is the correct / more-correct implementation
     394        double apexValue = _directory.getDouble(ExifSubIFDDirectory.TAG_SHUTTER_SPEED);
     395        double apexPower = Math.pow(2.0, apexValue);
     396
     397        StringBuffer sb = new StringBuffer();
     398        if (apexPower > 1)
     399            apexPower = Math.floor(apexPower);
     400
     401        if (apexPower < 1) {
     402            sb.append((int)Math.round(1/apexPower));
     403        } else {
     404            sb.append("1/");
     405            sb.append((int)apexPower);
     406        }
     407        sb.append(" sec");
     408        return sb.toString();
     409*/
     410    }
     411
     412    // EXIF LightSource
     413    @Nullable
     414    protected String getLightSourceDescription(short wbtype)
     415    {
     416        switch (wbtype)
     417        {
     418            case 0:
     419                return "Unknown";
     420            case 1:
     421                return "Daylight";
     422            case 2:
     423                return "Fluorescent";
     424            case 3:
     425                return "Tungsten (Incandescent)";
     426            case 4:
     427                return "Flash";
     428            case 9:
     429                return "Fine Weather";
     430            case 10:
     431                return "Cloudy";
     432            case 11:
     433                return "Shade";
     434            case 12:
     435                return "Daylight Fluorescent";    // (D 5700 - 7100K)
     436            case 13:
     437                return "Day White Fluorescent";   // (N 4600 - 5500K)
     438            case 14:
     439                return "Cool White Fluorescent";  // (W 3800 - 4500K)
     440            case 15:
     441                return "White Fluorescent";       // (WW 3250 - 3800K)
     442            case 16:
     443                return "Warm White Fluorescent";  // (L 2600 - 3250K)
     444            case 17:
     445                return "Standard Light A";
     446            case 18:
     447                return "Standard Light B";
     448            case 19:
     449                return "Standard Light C";
     450            case 20:
     451                return "D55";
     452            case 21:
     453                return "D65";
     454            case 22:
     455                return "D75";
     456            case 23:
     457                return "D50";
     458            case 24:
     459                return "ISO Studio Tungsten";
     460            case 255:
     461                return "Other";
     462        }
     463
     464        return getDescription(wbtype);
     465    }
    347466}
  • trunk/src/com/drew/metadata/exif/ExifDescriptorBase.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2626import com.drew.lang.annotations.NotNull;
    2727import com.drew.lang.annotations.Nullable;
     28import com.drew.lang.ByteArrayReader;
    2829import com.drew.metadata.Directory;
    2930import com.drew.metadata.TagDescriptor;
    3031
     32import java.io.IOException;
    3133import java.io.UnsupportedEncodingException;
    32 import java.math.RoundingMode;
    3334import java.text.DecimalFormat;
    3435import java.util.HashMap;
     
    4243 * @author Drew Noakes https://drewnoakes.com
    4344 */
     45@SuppressWarnings("WeakerAccess")
    4446public abstract class ExifDescriptorBase<T extends Directory> extends TagDescriptor<T>
    4547{
     
    4951     */
    5052    private final boolean _allowDecimalRepresentationOfRationals = true;
    51 
    52     @NotNull
    53     private static final java.text.DecimalFormat SimpleDecimalFormatter = new DecimalFormat("0.#");
    5453
    5554    // Note for the potential addition of brightness presentation in eV:
     
    123122            case TAG_FILL_ORDER:
    124123                return getFillOrderDescription();
     124            case TAG_CFA_PATTERN_2:
     125                return getCfaPattern2Description();
    125126            case TAG_EXPOSURE_TIME:
    126127                return getExposureTimeDescription();
     
    167168            case TAG_SCENE_TYPE:
    168169                return getSceneTypeDescription();
     170            case TAG_CFA_PATTERN:
     171                return getCfaPatternDescription();
    169172            case TAG_COMPONENTS_CONFIGURATION:
    170173                return getComponentConfigurationDescription();
     
    234237    public String getReferenceBlackWhiteDescription()
    235238    {
     239        // For some reason, sometimes this is read as a long[] and
     240        // getIntArray isn't able to deal with it
    236241        int[] ints = _directory.getIntArray(TAG_REFERENCE_BLACK_WHITE);
    237242        if (ints==null || ints.length < 6)
    238             return null;
     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
    239259        int blackR = ints[0];
    240260        int whiteR = ints[1];
     
    279299    public String getOrientationDescription()
    280300    {
    281         return getIndexedDescription(TAG_ORIENTATION, 1,
    282             "Top, left side (Horizontal / normal)",
    283             "Top, right side (Mirror horizontal)",
    284             "Bottom, right side (Rotate 180)",
    285             "Bottom, left side (Mirror vertical)",
    286             "Left side, top (Mirror horizontal and rotate 270 CW)",
    287             "Right side, top (Rotate 90 CW)",
    288             "Right side, bottom (Mirror horizontal and rotate 90 CW)",
    289             "Left side, bottom (Rotate 270 CW)");
     301        return super.getOrientationDescription(TAG_ORIENTATION);
    290302    }
    291303
     
    443455    public String getNewSubfileTypeDescription()
    444456    {
    445         return getIndexedDescription(TAG_NEW_SUBFILE_TYPE, 1,
     457        return getIndexedDescription(TAG_NEW_SUBFILE_TYPE, 0,
    446458            "Full-resolution image",
    447459            "Reduced-resolution image",
     460            "Single page of multi-page image",
    448461            "Single page of multi-page reduced-resolution image",
    449462            "Transparency mask",
     
    587600            : value.getNumerator() == 0
    588601                ? "Digital zoom not used"
    589                 : SimpleDecimalFormatter.format(value.doubleValue());
     602                : new DecimalFormat("0.#").format(value.doubleValue());
    590603    }
    591604
     
    689702            "Directly photographed image"
    690703        );
     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;
    691860    }
    692861
     
    10021171    public String getShutterSpeedDescription()
    10031172    {
    1004         // I believe this method to now be stable, but am leaving some alternative snippets of
    1005         // code in here, to assist anyone who's looking into this (given that I don't have a public CVS).
    1006 
    1007 //        float apexValue = _directory.getFloat(ExifSubIFDDirectory.TAG_SHUTTER_SPEED);
    1008 //        int apexPower = (int)Math.pow(2.0, apexValue);
    1009 //        return "1/" + apexPower + " sec";
    1010         // TODO test this method
    1011         // thanks to Mark Edwards for spotting and patching a bug in the calculation of this
    1012         // description (spotted bug using a Canon EOS 300D)
    1013         // thanks also to Gli Blr for spotting this bug
    1014         Float apexValue = _directory.getFloatObject(TAG_SHUTTER_SPEED);
    1015         if (apexValue == null)
    1016             return null;
    1017         if (apexValue <= 1) {
    1018             float apexPower = (float)(1 / (Math.exp(apexValue * Math.log(2))));
    1019             long apexPower10 = Math.round((double)apexPower * 10.0);
    1020             float fApexPower = (float)apexPower10 / 10.0f;
    1021             DecimalFormat format = new DecimalFormat("0.##");
    1022             format.setRoundingMode(RoundingMode.HALF_UP);
    1023             return format.format(fApexPower) + " sec";
    1024         } else {
    1025             int apexPower = (int)((Math.exp(apexValue * Math.log(2))));
    1026             return "1/" + apexPower + " sec";
    1027         }
    1028 
    1029 /*
    1030         // This alternative implementation offered by Bill Richards
    1031         // TODO determine which is the correct / more-correct implementation
    1032         double apexValue = _directory.getDouble(ExifSubIFDDirectory.TAG_SHUTTER_SPEED);
    1033         double apexPower = Math.pow(2.0, apexValue);
    1034 
    1035         StringBuffer sb = new StringBuffer();
    1036         if (apexPower > 1)
    1037             apexPower = Math.floor(apexPower);
    1038 
    1039         if (apexPower < 1) {
    1040             sb.append((int)Math.round(1/apexPower));
    1041         } else {
    1042             sb.append("1/");
    1043             sb.append((int)apexPower);
    1044         }
    1045         sb.append(" sec");
    1046         return sb.toString();
    1047 */
     1173        return super.getShutterSpeedDescription(TAG_SHUTTER_SPEED);
    10481174    }
    10491175
  • trunk/src/com/drew/metadata/exif/ExifDirectoryBase.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3131 * @author Drew Noakes https://drewnoakes.com
    3232 */
     33@SuppressWarnings("WeakerAccess")
    3334public abstract class ExifDirectoryBase extends Directory
    3435{
     
    126127
    127128    public static final int TAG_RESOLUTION_UNIT                   = 0x0128;
     129    public static final int TAG_PAGE_NUMBER                       = 0x0129;
     130
    128131    public static final int TAG_TRANSFER_FUNCTION                 = 0x012D;
    129132    public static final int TAG_SOFTWARE                          = 0x0131;
     
    149152    public static final int TAG_JPEG_TABLES                       = 0x015B;
    150153    public static final int TAG_JPEG_PROC                         = 0x0200;
     154
     155    // 0x0201 can have all kinds of descriptions for thumbnail starting index
     156    // 0x0202 can have all kinds of descriptions for thumbnail length
     157    public static final int TAG_JPEG_RESTART_INTERVAL = 0x0203;
     158    public static final int TAG_JPEG_LOSSLESS_PREDICTORS = 0x0205;
     159    public static final int TAG_JPEG_POINT_TRANSFORMS = 0x0206;
     160    public static final int TAG_JPEG_Q_TABLES = 0x0207;
     161    public static final int TAG_JPEG_DC_TABLES = 0x0208;
     162    public static final int TAG_JPEG_AC_TABLES = 0x0209;
    151163
    152164    public static final int TAG_YCBCR_COEFFICIENTS                = 0x0211;
     
    266278    public static final int TAG_METERING_MODE                     = 0x9207;
    267279
     280    /**
     281     * @deprecated use {@link com.drew.metadata.exif.ExifDirectoryBase#TAG_WHITE_BALANCE} instead.
     282     */
     283    @Deprecated
     284    public static final int TAG_LIGHT_SOURCE                      = 0x9208;
    268285    /**
    269286     * White balance (aka light source). '0' means unknown, '1' daylight,
     
    574591    public static final int TAG_GAMMA                             = 0xA500;
    575592
    576     public static final int TAG_PRINT_IM                          = 0xC4A5;
     593    public static final int TAG_PRINT_IMAGE_MATCHING_INFO         = 0xC4A5;
    577594
    578595    public static final int TAG_PANASONIC_TITLE                   = 0xC6D2;
     
    612629        map.put(TAG_PAGE_NAME, "Page Name");
    613630        map.put(TAG_RESOLUTION_UNIT, "Resolution Unit");
     631        map.put(TAG_PAGE_NUMBER, "Page Number");
    614632        map.put(TAG_TRANSFER_FUNCTION, "Transfer Function");
    615633        map.put(TAG_SOFTWARE, "Software");
     
    628646        map.put(TAG_JPEG_TABLES, "JPEG Tables");
    629647        map.put(TAG_JPEG_PROC, "JPEG Proc");
     648
     649        map.put(TAG_JPEG_RESTART_INTERVAL, "JPEG Restart Interval");
     650        map.put(TAG_JPEG_LOSSLESS_PREDICTORS, "JPEG Lossless Predictors");
     651        map.put(TAG_JPEG_POINT_TRANSFORMS, "JPEG Point Transforms");
     652        map.put(TAG_JPEG_Q_TABLES, "JPEGQ Tables");
     653        map.put(TAG_JPEG_DC_TABLES, "JPEGDC Tables");
     654        map.put(TAG_JPEG_AC_TABLES, "JPEGAC Tables");
     655
    630656        map.put(TAG_YCBCR_COEFFICIENTS, "YCbCr Coefficients");
    631657        map.put(TAG_YCBCR_SUBSAMPLING, "YCbCr Sub-Sampling");
     
    730756        map.put(TAG_LENS_SERIAL_NUMBER, "Lens Serial Number");
    731757        map.put(TAG_GAMMA, "Gamma");
    732         map.put(TAG_PRINT_IM, "Print IM");
     758        map.put(TAG_PRINT_IMAGE_MATCHING_INFO, "Print Image Matching (PIM) Info");
    733759        map.put(TAG_PANASONIC_TITLE, "Panasonic Title");
    734760        map.put(TAG_PANASONIC_TITLE_2, "Panasonic Title (2)");
  • trunk/src/com/drew/metadata/exif/ExifIFD0Descriptor.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2929 * @author Drew Noakes https://drewnoakes.com
    3030 */
     31@SuppressWarnings("WeakerAccess")
    3132public class ExifIFD0Descriptor extends ExifDescriptorBase<ExifIFD0Directory>
    3233{
  • trunk/src/com/drew/metadata/exif/ExifIFD0Directory.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3131 * @author Drew Noakes https://drewnoakes.com
    3232 */
     33@SuppressWarnings("WeakerAccess")
    3334public class ExifIFD0Directory extends ExifDirectoryBase
    3435{
  • trunk/src/com/drew/metadata/exif/ExifInteropDescriptor.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2828 * @author Drew Noakes https://drewnoakes.com
    2929 */
     30@SuppressWarnings("WeakerAccess")
    3031public class ExifInteropDescriptor extends ExifDescriptorBase<ExifInteropDirectory>
    3132{
  • trunk/src/com/drew/metadata/exif/ExifInteropDirectory.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3030 * @author Drew Noakes https://drewnoakes.com
    3131 */
     32@SuppressWarnings("WeakerAccess")
    3233public class ExifInteropDirectory extends ExifDirectoryBase
    3334{
  • trunk/src/com/drew/metadata/exif/ExifReader.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    4242 * @author Drew Noakes https://drewnoakes.com
    4343 */
     44@SuppressWarnings("WeakerAccess")
    4445public class ExifReader implements JpegSegmentMetadataReader
    4546{
    4647    /** Exif data stored in JPEG files' APP1 segment are preceded by this six character preamble. */
    4748    public static final String JPEG_SEGMENT_PREAMBLE = "Exif\0\0";
    48 
    49     private boolean _storeThumbnailBytes = true;
    50 
    51     public boolean isStoreThumbnailBytes()
    52     {
    53         return _storeThumbnailBytes;
    54     }
    55 
    56     public void setStoreThumbnailBytes(boolean storeThumbnailBytes)
    57     {
    58         _storeThumbnailBytes = storeThumbnailBytes;
    59     }
    6049
    6150    @NotNull
     
    8978    }
    9079
    91     /** Reads TIFF formatted Exif data a specified offset within a {@link RandomAccessReader}. */
     80    /** Reads TIFF formatted Exif data at a specified offset within a {@link RandomAccessReader}. */
    9281    public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata, int readerOffset, @Nullable Directory parentDirectory)
    9382    {
     83        ExifTiffHandler exifTiffHandler = new ExifTiffHandler(metadata, parentDirectory);
     84
    9485        try {
    9586            // Read the TIFF-formatted Exif data
    9687            new TiffReader().processTiff(
    9788                reader,
    98                 new ExifTiffHandler(metadata, _storeThumbnailBytes, parentDirectory),
     89                exifTiffHandler,
    9990                readerOffset
    10091            );
    10192        } catch (TiffProcessingException e) {
     93            exifTiffHandler.error("Exception processing TIFF data: " + e.getMessage());
    10294            // TODO what do to with this error state?
    10395            e.printStackTrace(System.err);
    10496        } catch (IOException e) {
     97            exifTiffHandler.error("Exception processing TIFF data: " + e.getMessage());
    10598            // TODO what do to with this error state?
    10699            e.printStackTrace(System.err);
  • trunk/src/com/drew/metadata/exif/ExifSubIFDDescriptor.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2828 * @author Drew Noakes https://drewnoakes.com
    2929 */
     30@SuppressWarnings("WeakerAccess")
    3031public class ExifSubIFDDescriptor extends ExifDescriptorBase<ExifSubIFDDirectory>
    3132{
  • trunk/src/com/drew/metadata/exif/ExifSubIFDDirectory.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2121package com.drew.metadata.exif;
    2222
     23import com.drew.lang.annotations.NotNull;
     24import com.drew.lang.annotations.Nullable;
     25
    2326import java.util.Date;
    2427import java.util.HashMap;
    2528import java.util.TimeZone;
    26 
    27 import com.drew.lang.annotations.NotNull;
    28 import com.drew.lang.annotations.Nullable;
    2929
    3030/**
     
    3333 * @author Drew Noakes https://drewnoakes.com
    3434 */
     35@SuppressWarnings("WeakerAccess")
    3536public class ExifSubIFDDirectory extends ExifDirectoryBase
    3637{
     
    8889     */
    8990    @Nullable
    90     public Date getDateOriginal(TimeZone timeZone)
     91    public Date getDateOriginal(@Nullable TimeZone timeZone)
    9192    {
    9293        return getDate(TAG_DATETIME_ORIGINAL, getString(TAG_SUBSECOND_TIME_ORIGINAL), timeZone);
     
    116117     */
    117118    @Nullable
    118     public Date getDateDigitized(TimeZone timeZone)
     119    public Date getDateDigitized(@Nullable TimeZone timeZone)
    119120    {
    120121        return getDate(TAG_DATETIME_DIGITIZED, getString(TAG_SUBSECOND_TIME_DIGITIZED), timeZone);
  • trunk/src/com/drew/metadata/exif/ExifThumbnailDescriptor.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3333 * @author Drew Noakes https://drewnoakes.com
    3434 */
     35@SuppressWarnings("WeakerAccess")
    3536public class ExifThumbnailDescriptor extends ExifDescriptorBase<ExifThumbnailDirectory>
    3637{
  • trunk/src/com/drew/metadata/exif/ExifThumbnailDirectory.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2222package com.drew.metadata.exif;
    2323
    24 import java.io.FileOutputStream;
    25 import java.io.IOException;
     24import com.drew.lang.annotations.NotNull;
     25
    2626import java.util.HashMap;
    27 
    28 import com.drew.lang.annotations.NotNull;
    29 import com.drew.lang.annotations.Nullable;
    30 import com.drew.metadata.MetadataException;
    3127
    3228/**
     
    3531 * @author Drew Noakes https://drewnoakes.com
    3632 */
     33@SuppressWarnings("WeakerAccess")
    3734public class ExifThumbnailDirectory extends ExifDirectoryBase
    3835{
     
    4643    public static final int TAG_THUMBNAIL_LENGTH = 0x0202;
    4744
     45    /**
     46     * @deprecated use {@link com.drew.metadata.exif.ExifDirectoryBase#TAG_COMPRESSION} instead.
     47     */
     48    @Deprecated
     49    public static final int TAG_THUMBNAIL_COMPRESSION = 0x0103;
     50
    4851    @NotNull
    4952    protected static final HashMap<Integer, String> _tagNameMap = new HashMap<Integer, String>();
     
    5659        _tagNameMap.put(TAG_THUMBNAIL_LENGTH, "Thumbnail Length");
    5760    }
    58 
    59     @Nullable
    60     private byte[] _thumbnailData;
    6161
    6262    public ExifThumbnailDirectory()
     
    7878        return _tagNameMap;
    7979    }
    80 
    81     public boolean hasThumbnailData()
    82     {
    83         return _thumbnailData != null;
    84     }
    85 
    86     @Nullable
    87     public byte[] getThumbnailData()
    88     {
    89         return _thumbnailData;
    90     }
    91 
    92     public void setThumbnailData(@Nullable byte[] data)
    93     {
    94         _thumbnailData = data;
    95     }
    96 
    97     public void writeThumbnail(@NotNull String filename) throws MetadataException, IOException
    98     {
    99         byte[] data = _thumbnailData;
    100 
    101         if (data == null)
    102             throw new MetadataException("No thumbnail data exists.");
    103 
    104         FileOutputStream stream = null;
    105         try {
    106             stream = new FileOutputStream(filename);
    107             stream.write(data);
    108         } finally {
    109             if (stream != null)
    110                 stream.close();
    111         }
    112     }
    113 
    114 /*
    115     // This thumbnail extraction code is not complete, and is included to assist anyone who feels like looking into
    116     // it.  Please share any progress with the original author, and hence the community.  Thanks.
    117 
    118     public Image getThumbnailImage() throws MetadataException
    119     {
    120         if (!hasThumbnailData())
    121             return null;
    122 
    123         int compression = 0;
    124         try {
    125             compression = this.getInt(ExifSubIFDDirectory.TAG_COMPRESSION);
    126         } catch (Throwable e) {
    127             this.addError("Unable to determine thumbnail type " + e.getMessage());
    128         }
    129 
    130         final byte[] thumbnailBytes = getThumbnailData();
    131 
    132         if (compression == ExifSubIFDDirectory.COMPRESSION_JPEG)
    133         {
    134             // JPEG Thumbnail
    135             // operate directly on thumbnailBytes
    136             return decodeBytesAsImage(thumbnailBytes);
    137         }
    138         else if (compression == ExifSubIFDDirectory.COMPRESSION_NONE)
    139         {
    140             // uncompressed thumbnail (raw RGB data)
    141             if (!this.containsTag(ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION))
    142                 return null;
    143 
    144             try
    145             {
    146                 // If the image is RGB format, then convert it to a bitmap
    147                 final int photometricInterpretation = this.getInt(ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION);
    148                 if (photometricInterpretation == ExifSubIFDDirectory.PHOTOMETRIC_INTERPRETATION_RGB)
    149                 {
    150                     // RGB
    151                     Image image = createImageFromRawRgb(thumbnailBytes);
    152                     return image;
    153                 }
    154                 else if (photometricInterpretation == ExifSubIFDDirectory.PHOTOMETRIC_INTERPRETATION_YCBCR)
    155                 {
    156                     // YCbCr
    157                     Image image = createImageFromRawYCbCr(thumbnailBytes);
    158                     return image;
    159                 }
    160                 else if (photometricInterpretation == ExifSubIFDDirectory.PHOTOMETRIC_INTERPRETATION_MONOCHROME)
    161                 {
    162                     // Monochrome
    163                     return null;
    164                 }
    165             } catch (Throwable e) {
    166                 this.addError("Unable to extract thumbnail: " + e.getMessage());
    167             }
    168         }
    169         return null;
    170     }
    171 
    172     /**
    173      * Handle the YCbCr thumbnail encoding used by Ricoh RDC4200/4300, Fuji DS-7/300 and DX-5/7/9 cameras.
    174      *
    175      * At DX-5/7/9, YCbCrSubsampling(0x0212) has values of '2,1', PlanarConfiguration(0x011c) has a value '1'. So the
    176      * data align of this image is below.
    177      *
    178      * Y(0,0),Y(1,0),Cb(0,0),Cr(0,0), Y(2,0),Y(3,0),Cb(2,0),Cr(3.0), Y(4,0),Y(5,0),Cb(4,0),Cr(4,0). . . .
    179      *
    180      * The numbers in parenthesis are pixel coordinates. DX series' YCbCrCoefficients(0x0211) has values '0.299/0.587/0.114',
    181      * ReferenceBlackWhite(0x0214) has values '0,255,128,255,128,255'. Therefore to convert from Y/Cb/Cr to RGB is;
    182      *
    183      * B(0,0)=(Cb-128)*(2-0.114*2)+Y(0,0)
    184      * R(0,0)=(Cr-128)*(2-0.299*2)+Y(0,0)
    185      * G(0,0)=(Y(0,0)-0.114*B(0,0)-0.299*R(0,0))/0.587
    186      *
    187      * Horizontal subsampling is a value '2', so you can calculate B(1,0)/R(1,0)/G(1,0) by using the Y(1,0) and Cr(0,0)/Cb(0,0).
    188      * Repeat this conversion by value of ImageWidth(0x0100) and ImageLength(0x0101).
    189      *
    190      * @param thumbnailBytes
    191      * @return
    192      * @throws com.drew.metadata.MetadataException
    193      * /
    194     private Image createImageFromRawYCbCr(byte[] thumbnailBytes) throws MetadataException
    195     {
    196         /*
    197             Y  =  0.257R + 0.504G + 0.098B + 16
    198             Cb = -0.148R - 0.291G + 0.439B + 128
    199             Cr =  0.439R - 0.368G - 0.071B + 128
    200 
    201             G = 1.164(Y-16) - 0.391(Cb-128) - 0.813(Cr-128)
    202             R = 1.164(Y-16) + 1.596(Cr-128)
    203             B = 1.164(Y-16) + 2.018(Cb-128)
    204 
    205             R, G and B range from 0 to 255.
    206             Y ranges from 16 to 235.
    207             Cb and Cr range from 16 to 240.
    208 
    209             http://www.faqs.org/faqs/graphics/colorspace-faq/
    210         * /
    211 
    212         int length = thumbnailBytes.length; // this.getInt(ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS);
    213         final int imageWidth = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_WIDTH);
    214         final int imageHeight = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT);
    215 //        final int headerLength = 54;
    216 //        byte[] result = new byte[length + headerLength];
    217 //        // Add a windows BMP header described:
    218 //        // http://www.onicos.com/staff/iz/formats/bmp.html
    219 //        result[0] = 'B';
    220 //        result[1] = 'M'; // File Type identifier
    221 //        result[3] = (byte)(result.length / 256);
    222 //        result[2] = (byte)result.length;
    223 //        result[10] = (byte)headerLength;
    224 //        result[14] = 40; // MS Windows BMP header
    225 //        result[18] = (byte)imageWidth;
    226 //        result[22] = (byte)imageHeight;
    227 //        result[26] = 1;  // 1 Plane
    228 //        result[28] = 24; // Colour depth
    229 //        result[34] = (byte)length;
    230 //        result[35] = (byte)(length / 256);
    231 
    232         final BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
    233 
    234         // order is YCbCr and image is upside down, bitmaps are BGR
    235 ////        for (int i = headerLength, dataOffset = length; i<result.length; i += 3, dataOffset -= 3)
    236 //        {
    237 //            final int y =  thumbnailBytes[dataOffset - 2] & 0xFF;
    238 //            final int cb = thumbnailBytes[dataOffset - 1] & 0xFF;
    239 //            final int cr = thumbnailBytes[dataOffset] & 0xFF;
    240 //            if (y<16 || y>235 || cb<16 || cb>240 || cr<16 || cr>240)
    241 //                "".toString();
    242 //
    243 //            int g = (int)(1.164*(y-16) - 0.391*(cb-128) - 0.813*(cr-128));
    244 //            int r = (int)(1.164*(y-16) + 1.596*(cr-128));
    245 //            int b = (int)(1.164*(y-16) + 2.018*(cb-128));
    246 //
    247 ////            result[i] = (byte)b;
    248 ////            result[i + 1] = (byte)g;
    249 ////            result[i + 2] = (byte)r;
    250 //
    251 //            // TODO compose the image here
    252 //            image.setRGB(1, 2, 3);
    253 //        }
    254 
    255         return image;
    256     }
    257 
    258     /**
    259      * Creates a thumbnail image in (Windows) BMP format from raw RGB data.
    260      * @param thumbnailBytes
    261      * @return
    262      * @throws com.drew.metadata.MetadataException
    263      * /
    264     private Image createImageFromRawRgb(byte[] thumbnailBytes) throws MetadataException
    265     {
    266         final int length = thumbnailBytes.length; // this.getInt(ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS);
    267         final int imageWidth = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_WIDTH);
    268         final int imageHeight = this.getInt(ExifSubIFDDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT);
    269 //        final int headerLength = 54;
    270 //        final byte[] result = new byte[length + headerLength];
    271 //        // Add a windows BMP header described:
    272 //        // http://www.onicos.com/staff/iz/formats/bmp.html
    273 //        result[0] = 'B';
    274 //        result[1] = 'M'; // File Type identifier
    275 //        result[3] = (byte)(result.length / 256);
    276 //        result[2] = (byte)result.length;
    277 //        result[10] = (byte)headerLength;
    278 //        result[14] = 40; // MS Windows BMP header
    279 //        result[18] = (byte)imageWidth;
    280 //        result[22] = (byte)imageHeight;
    281 //        result[26] = 1;  // 1 Plane
    282 //        result[28] = 24; // Colour depth
    283 //        result[34] = (byte)length;
    284 //        result[35] = (byte)(length / 256);
    285 
    286         final BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
    287 
    288         // order is RGB and image is upside down, bitmaps are BGR
    289 //        for (int i = headerLength, dataOffset = length; i<result.length; i += 3, dataOffset -= 3)
    290 //        {
    291 //            byte b = thumbnailBytes[dataOffset - 2];
    292 //            byte g = thumbnailBytes[dataOffset - 1];
    293 //            byte r = thumbnailBytes[dataOffset];
    294 //
    295 //            // TODO compose the image here
    296 //            image.setRGB(1, 2, 3);
    297 //        }
    298 
    299         return image;
    300     }
    301 */
    30280}
  • trunk/src/com/drew/metadata/exif/ExifTiffHandler.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2121package com.drew.metadata.exif;
    2222
     23import java.io.ByteArrayInputStream;
     24import java.io.IOException;
     25import java.util.Arrays;
     26import java.util.Set;
     27
     28import com.drew.imaging.jpeg.JpegMetadataReader;
     29import com.drew.imaging.jpeg.JpegProcessingException;
    2330import com.drew.imaging.tiff.TiffProcessingException;
    2431import com.drew.imaging.tiff.TiffReader;
     32import com.drew.lang.Charsets;
    2533import com.drew.lang.RandomAccessReader;
    2634import com.drew.lang.SequentialByteArrayReader;
     
    2937import com.drew.metadata.Directory;
    3038import com.drew.metadata.Metadata;
     39import com.drew.metadata.StringValue;
    3140import com.drew.metadata.exif.makernotes.*;
    3241import com.drew.metadata.iptc.IptcReader;
    3342import com.drew.metadata.tiff.DirectoryTiffHandler;
    34 
    35 import java.io.IOException;
    36 import java.util.Set;
    3743
    3844/**
     
    4652public class ExifTiffHandler extends DirectoryTiffHandler
    4753{
    48     private final boolean _storeThumbnailBytes;
    49 
    50     public ExifTiffHandler(@NotNull Metadata metadata, boolean storeThumbnailBytes, @Nullable Directory parentDirectory)
    51     {
    52         super(metadata, ExifIFD0Directory.class);
    53         _storeThumbnailBytes = storeThumbnailBytes;
     54    public ExifTiffHandler(@NotNull Metadata metadata, @Nullable Directory parentDirectory)
     55    {
     56        super(metadata);
    5457
    5558        if (parentDirectory != null)
     
    6467        final int panasonicRawTiffMarker = 0x0055; // for RW2 files
    6568
    66         if (marker != standardTiffMarker && marker != olympusRawTiffMarker && marker != olympusRawTiffMarker2 && marker != panasonicRawTiffMarker) {
    67             throw new TiffProcessingException("Unexpected TIFF marker: 0x" + Integer.toHexString(marker));
     69        switch (marker)
     70        {
     71            case standardTiffMarker:
     72            case olympusRawTiffMarker:      // Todo: implement an IFD0, if there is one
     73            case olympusRawTiffMarker2:     // Todo: implement an IFD0, if there is one
     74                pushDirectory(ExifIFD0Directory.class);
     75                break;
     76            case panasonicRawTiffMarker:
     77                pushDirectory(PanasonicRawIFD0Directory.class);
     78                break;
     79            default:
     80                throw new TiffProcessingException(String.format("Unexpected TIFF marker: 0x%X", marker));
    6881        }
    6982    }
     
    7689        }
    7790
    78         if (_currentDirectory instanceof ExifIFD0Directory) {
     91        if (_currentDirectory instanceof ExifIFD0Directory || _currentDirectory instanceof PanasonicRawIFD0Directory) {
    7992            if (tagId == ExifIFD0Directory.TAG_EXIF_SUB_IFD_OFFSET) {
    8093                pushDirectory(ExifSubIFDDirectory.class);
     
    96109
    97110        if (_currentDirectory instanceof OlympusMakernoteDirectory) {
    98             if (tagId == OlympusMakernoteDirectory.TAG_EQUIPMENT) {
    99                 pushDirectory(OlympusEquipmentMakernoteDirectory.class);
    100                 return true;
    101             }
    102 
    103             if (tagId == OlympusMakernoteDirectory.TAG_CAMERA_SETTINGS) {
    104                 pushDirectory(OlympusCameraSettingsMakernoteDirectory.class);
    105                 return true;
     111            // Note: these also appear in customProcessTag because some are IFD pointers while others begin immediately
     112            // for the same directories
     113            switch(tagId) {
     114                case OlympusMakernoteDirectory.TAG_EQUIPMENT:
     115                    pushDirectory(OlympusEquipmentMakernoteDirectory.class);
     116                    return true;
     117                case OlympusMakernoteDirectory.TAG_CAMERA_SETTINGS:
     118                    pushDirectory(OlympusCameraSettingsMakernoteDirectory.class);
     119                    return true;
     120                case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT:
     121                    pushDirectory(OlympusRawDevelopmentMakernoteDirectory.class);
     122                    return true;
     123                case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT_2:
     124                    pushDirectory(OlympusRawDevelopment2MakernoteDirectory.class);
     125                    return true;
     126                case OlympusMakernoteDirectory.TAG_IMAGE_PROCESSING:
     127                    pushDirectory(OlympusImageProcessingMakernoteDirectory.class);
     128                    return true;
     129                case OlympusMakernoteDirectory.TAG_FOCUS_INFO:
     130                    pushDirectory(OlympusFocusInfoMakernoteDirectory.class);
     131                    return true;
     132                case OlympusMakernoteDirectory.TAG_RAW_INFO:
     133                    pushDirectory(OlympusRawInfoMakernoteDirectory.class);
     134                    return true;
     135                case OlympusMakernoteDirectory.TAG_MAIN_INFO:
     136                    pushDirectory(OlympusMakernoteDirectory.class);
     137                    return true;
    106138            }
    107139        }
     
    113145    {
    114146        // In Exif, the only known 'follower' IFD is the thumbnail one, however this may not be the case.
    115         if (_currentDirectory instanceof ExifIFD0Directory) {
    116             pushDirectory(ExifThumbnailDirectory.class);
     147        // UPDATE: In multipage TIFFs, the 'follower' IFD points to the next image in the set
     148        if (_currentDirectory instanceof ExifIFD0Directory || _currentDirectory instanceof ExifImageDirectory) {
     149            // If the PageNumber tag is defined, assume this is a multipage TIFF or similar
     150            // TODO: Find better ways to know which follower Directory should be used
     151            if (_currentDirectory.containsTag(ExifDirectoryBase.TAG_PAGE_NUMBER))
     152                pushDirectory(ExifImageDirectory.class);
     153            else
     154                pushDirectory(ExifThumbnailDirectory.class);
    117155            return true;
    118156        }
     
    132170        if (formatCode == 13)
    133171            return componentCount * 4;
     172
     173        // an unknown (0) formatCode needs to be potentially handled later as a highly custom directory tag
     174        if(formatCode == 0)
     175            return 0L;
    134176
    135177        return null;
     
    143185                                    final int byteCount) throws IOException
    144186    {
     187        // Some 0x0000 tags have a 0 byteCount. Determine whether it's bad.
     188        if (tagId == 0)
     189        {
     190            if (_currentDirectory.containsTag(tagId))
     191            {
     192                // Let it go through for now. Some directories handle it, some don't
     193                return false;
     194            }
     195
     196            // Skip over 0x0000 tags that don't have any associated bytes. No idea what it contains in this case, if anything.
     197            if (byteCount == 0)
     198                return true;
     199        }
     200
    145201        // Custom processing for the Makernote tag
    146202        if (tagId == ExifSubIFDDirectory.TAG_MAKERNOTE && _currentDirectory instanceof ExifSubIFDDirectory) {
     
    159215        }
    160216
     217        if (HandlePrintIM(_currentDirectory, tagId))
     218        {
     219            PrintIMDirectory printIMDirectory = new PrintIMDirectory();
     220            printIMDirectory.setParent(_currentDirectory);
     221            _metadata.addDirectory(printIMDirectory);
     222            ProcessPrintIM(printIMDirectory, tagOffset, reader, byteCount);
     223            return true;
     224        }
     225
     226        // Note: these also appear in tryEnterSubIfd because some are IFD pointers while others begin immediately
     227        // for the same directories
     228        if(_currentDirectory instanceof OlympusMakernoteDirectory)
     229        {
     230            switch (tagId)
     231            {
     232                case OlympusMakernoteDirectory.TAG_EQUIPMENT:
     233                    pushDirectory(OlympusEquipmentMakernoteDirectory.class);
     234                    TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
     235                    return true;
     236                case OlympusMakernoteDirectory.TAG_CAMERA_SETTINGS:
     237                    pushDirectory(OlympusCameraSettingsMakernoteDirectory.class);
     238                    TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
     239                    return true;
     240                case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT:
     241                    pushDirectory(OlympusRawDevelopmentMakernoteDirectory.class);
     242                    TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
     243                    return true;
     244                case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT_2:
     245                    pushDirectory(OlympusRawDevelopment2MakernoteDirectory.class);
     246                    TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
     247                    return true;
     248                case OlympusMakernoteDirectory.TAG_IMAGE_PROCESSING:
     249                    pushDirectory(OlympusImageProcessingMakernoteDirectory.class);
     250                    TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
     251                    return true;
     252                case OlympusMakernoteDirectory.TAG_FOCUS_INFO:
     253                    pushDirectory(OlympusFocusInfoMakernoteDirectory.class);
     254                    TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
     255                    return true;
     256                case OlympusMakernoteDirectory.TAG_RAW_INFO:
     257                    pushDirectory(OlympusRawInfoMakernoteDirectory.class);
     258                    TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
     259                    return true;
     260                case OlympusMakernoteDirectory.TAG_MAIN_INFO:
     261                    pushDirectory(OlympusMakernoteDirectory.class);
     262                    TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
     263                    return true;
     264            }
     265        }
     266
     267        if (_currentDirectory instanceof PanasonicRawIFD0Directory)
     268        {
     269            // these contain binary data with specific offsets, and can't be processed as regular ifd's.
     270            // The binary data is broken into 'fake' tags and there is a pattern.
     271            switch (tagId)
     272            {
     273                case PanasonicRawIFD0Directory.TagWbInfo:
     274                    PanasonicRawWbInfoDirectory dirWbInfo = new PanasonicRawWbInfoDirectory();
     275                    dirWbInfo.setParent(_currentDirectory);
     276                    _metadata.addDirectory(dirWbInfo);
     277                    ProcessBinary(dirWbInfo, tagOffset, reader, byteCount, false, 2);
     278                    return true;
     279                case PanasonicRawIFD0Directory.TagWbInfo2:
     280                    PanasonicRawWbInfo2Directory dirWbInfo2 = new PanasonicRawWbInfo2Directory();
     281                    dirWbInfo2.setParent(_currentDirectory);
     282                    _metadata.addDirectory(dirWbInfo2);
     283                    ProcessBinary(dirWbInfo2, tagOffset, reader, byteCount, false, 3);
     284                    return true;
     285                case PanasonicRawIFD0Directory.TagDistortionInfo:
     286                    PanasonicRawDistortionDirectory dirDistort = new PanasonicRawDistortionDirectory();
     287                    dirDistort.setParent(_currentDirectory);
     288                    _metadata.addDirectory(dirDistort);
     289                    ProcessBinary(dirDistort, tagOffset, reader, byteCount, true, 1);
     290                    return true;
     291            }
     292        }
     293
     294        // Panasonic RAW sometimes contains an embedded version of the data as a JPG file.
     295        if (tagId == PanasonicRawIFD0Directory.TagJpgFromRaw && _currentDirectory instanceof PanasonicRawIFD0Directory)
     296        {
     297            byte[] jpegrawbytes = reader.getBytes(tagOffset, byteCount);
     298
     299            // Extract information from embedded image since it is metadata-rich
     300            ByteArrayInputStream jpegmem = new ByteArrayInputStream(jpegrawbytes);
     301            try {
     302                Metadata jpegDirectory = JpegMetadataReader.readMetadata(jpegmem);
     303                for (Directory directory : jpegDirectory.getDirectories()) {
     304                    directory.setParent(_currentDirectory);
     305                    _metadata.addDirectory(directory);
     306                }
     307                return true;
     308            } catch (JpegProcessingException e) {
     309                _currentDirectory.addError("Error processing JpgFromRaw: " + e.getMessage());
     310            } catch (IOException e) {
     311                _currentDirectory.addError("Error reading JpgFromRaw: " + e.getMessage());
     312            }
     313        }
     314
    161315        return false;
    162316    }
    163317
    164     public void completed(@NotNull final RandomAccessReader reader, final int tiffHeaderOffset)
    165     {
    166         if (_storeThumbnailBytes) {
    167             // after the extraction process, if we have the correct tags, we may be able to store thumbnail information
    168             ExifThumbnailDirectory thumbnailDirectory = _metadata.getFirstDirectoryOfType(ExifThumbnailDirectory.class);
    169             if (thumbnailDirectory != null && thumbnailDirectory.containsTag(ExifThumbnailDirectory.TAG_COMPRESSION)) {
    170                 Integer offset = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET);
    171                 Integer length = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH);
    172                 if (offset != null && length != null) {
    173                     try {
    174                         byte[] thumbnailData = reader.getBytes(tiffHeaderOffset + offset, length);
    175                         thumbnailDirectory.setThumbnailData(thumbnailData);
    176                     } catch (IOException ex) {
    177                         thumbnailDirectory.addError("Invalid thumbnail data specification: " + ex.getMessage());
     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
     319    {
     320        // expects signed/unsigned int16 (for now)
     321        //int byteSize = issigned ? sizeof(short) : sizeof(ushort);
     322        int byteSize = 2;
     323
     324        // '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            {
     329                // 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)
     333                        directory.setObject(i, reader.getInt16(tagValueOffset + (i* byteSize)));
     334                    else
     335                        directory.setObject(i, reader.getUInt16(tagValueOffset + (i* byteSize)));
     336                }
     337                else
     338                {
     339                    // the next arrayLength bytes are a multi-byte value
     340                    if (issigned)
     341                    {
     342                        short[] val = new short[arrayLength];
     343                        for (int j = 0; j<val.length; j++)
     344                            val[j] = reader.getInt16(tagValueOffset + ((i + j) * byteSize));
     345                        directory.setObjectArray(i, val);
    178346                    }
     347                    else
     348                    {
     349                        int[] val = new int[arrayLength];
     350                        for (int j = 0; j<val.length; j++)
     351                            val[j] = reader.getUInt16(tagValueOffset + ((i + j) * byteSize));
     352                        directory.setObjectArray(i, val);
     353                    }
     354
     355                    i += arrayLength - 1;
    179356                }
    180357            }
     
    190367        Directory ifd0Directory = _metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
    191368
    192         if (ifd0Directory == null)
    193             return false;
    194 
    195         String cameraMake = ifd0Directory.getString(ExifIFD0Directory.TAG_MAKE);
    196 
    197         final String firstTwoChars = reader.getString(makernoteOffset, 2);
    198         final String firstThreeChars = reader.getString(makernoteOffset, 3);
    199         final String firstFourChars = reader.getString(makernoteOffset, 4);
    200         final String firstFiveChars = reader.getString(makernoteOffset, 5);
    201         final String firstSixChars = reader.getString(makernoteOffset, 6);
    202         final String firstSevenChars = reader.getString(makernoteOffset, 7);
    203         final String firstEightChars = reader.getString(makernoteOffset, 8);
    204         final String firstTenChars = reader.getString(makernoteOffset, 10);
    205         final String firstTwelveChars = reader.getString(makernoteOffset, 12);
     369        String cameraMake = ifd0Directory == null ? null : ifd0Directory.getString(ExifIFD0Directory.TAG_MAKE);
     370
     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);
    206381
    207382        boolean byteOrderBefore = reader.isMotorolaByteOrder();
     
    243418                        break;
    244419                    default:
    245                         ifd0Directory.addError("Unsupported Nikon makernote data ignored.");
     420                        _currentDirectory.addError("Unsupported Nikon makernote data ignored.");
    246421                        break;
    247422                }
     
    254429            pushDirectory(SonyType1MakernoteDirectory.class);
    255430            TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, tiffHeaderOffset);
     431        // Do this check LAST after most other Sony checks
     432        } else if (cameraMake != null && cameraMake.startsWith("SONY") &&
     433                !Arrays.equals(reader.getBytes(makernoteOffset, 2), new byte[]{ 0x01, 0x00 }) ) {
     434            // The IFD begins with the first Makernote byte (no ASCII name). Used in SR2 and ARW images
     435            pushDirectory(SonyType1MakernoteDirectory.class);
     436            TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset);
    256437        } else if ("SEMC MS\u0000\u0000\u0000\u0000\u0000".equals(firstTwelveChars)) {
    257438            // force MM for this directory
     
    294475        } else if ("LEICA".equals(firstFiveChars)) {
    295476            reader.setMotorolaByteOrder(false);
    296             if ("Leica Camera AG".equals(cameraMake)) {
     477
     478            // used by the X1/X2/X VARIO/T
     479            // (X1 starts with "LEICA\0\x01\0", Make is "LEICA CAMERA AG")
     480            // (X2 starts with "LEICA\0\x05\0", Make is "LEICA CAMERA AG")
     481            // (X VARIO starts with "LEICA\0\x04\0", Make is "LEICA CAMERA AG")
     482            // (T (Typ 701) starts with "LEICA\0\0x6", Make is "LEICA CAMERA AG")
     483            // (X (Typ 113) starts with "LEICA\0\0x7", Make is "LEICA CAMERA AG")
     484
     485            if ("LEICA\0\u0001\0".equals(firstEightChars) ||
     486                "LEICA\0\u0004\0".equals(firstEightChars) ||
     487                "LEICA\0\u0005\0".equals(firstEightChars) ||
     488                "LEICA\0\u0006\0".equals(firstEightChars) ||
     489                "LEICA\0\u0007\0".equals(firstEightChars))
     490            {
     491                pushDirectory(LeicaType5MakernoteDirectory.class);
     492                TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, makernoteOffset);
     493            } else if ("Leica Camera AG".equals(cameraMake)) {
    297494                pushDirectory(LeicaMakernoteDirectory.class);
    298495                TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset);
     
    304501                return false;
    305502            }
    306         } else if ("Panasonic\u0000\u0000\u0000".equals(reader.getString(makernoteOffset, 12))) {
     503        } else if ("Panasonic\u0000\u0000\u0000".equals(reader.getString(makernoteOffset, 12, Charsets.UTF_8))) {
    307504            // NON-Standard TIFF IFD Data using Panasonic Tags. There is no Next-IFD pointer after the IFD
    308505            // Offsets are relative to the start of the TIFF header at the beginning of the EXIF segment
     
    349546                TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, makernoteOffset);
    350547            }
     548        } else if (firstTenChars.equals("Apple iOS\0")) {
     549            // Always in Motorola byte order
     550            boolean orderBefore = reader.isMotorolaByteOrder();
     551            reader.setMotorolaByteOrder(true);
     552            pushDirectory(AppleMakernoteDirectory.class);
     553            TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 14, makernoteOffset);
     554            reader.setMotorolaByteOrder(orderBefore);
     555        } else if (reader.getUInt16(makernoteOffset) == ReconyxHyperFireMakernoteDirectory.MAKERNOTE_VERSION) {
     556            ReconyxHyperFireMakernoteDirectory directory = new ReconyxHyperFireMakernoteDirectory();
     557            _metadata.addDirectory(directory);
     558            processReconyxHyperFireMakernote(directory, makernoteOffset, reader);
     559        } else if (firstNineChars.equalsIgnoreCase("RECONYXUF")) {
     560            ReconyxUltraFireMakernoteDirectory directory = new ReconyxUltraFireMakernoteDirectory();
     561            _metadata.addDirectory(directory);
     562            processReconyxUltraFireMakernote(directory, makernoteOffset, reader);
     563        } else if ("SAMSUNG".equals(cameraMake)) {
     564            // Only handles Type2 notes correctly. Others aren't implemented, and it's complex to determine which ones to use
     565            pushDirectory(SamsungType2MakernoteDirectory.class);
     566            TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset);
    351567        } else {
    352568            // The makernote is not comprehended by this library.
     
    359575    }
    360576
     577    private static Boolean HandlePrintIM(@NotNull final Directory directory, final int tagId)
     578    {
     579        if (tagId == ExifDirectoryBase.TAG_PRINT_IMAGE_MATCHING_INFO)
     580            return true;
     581
     582        if (tagId == 0x0E00)
     583        {
     584            // Tempting to say every tagid of 0x0E00 is a PIM tag, but can't be 100% sure
     585            if (directory instanceof CasioType2MakernoteDirectory ||
     586                directory instanceof KyoceraMakernoteDirectory ||
     587                directory instanceof NikonType2MakernoteDirectory ||
     588                directory instanceof OlympusMakernoteDirectory ||
     589                directory instanceof PanasonicMakernoteDirectory ||
     590                directory instanceof PentaxMakernoteDirectory ||
     591                directory instanceof RicohMakernoteDirectory ||
     592                directory instanceof SanyoMakernoteDirectory ||
     593                directory instanceof SonyType1MakernoteDirectory)
     594                return true;
     595        }
     596
     597        return false;
     598    }
     599
     600    /// <summary>
     601    /// Process PrintIM IFD
     602    /// </summary>
     603    /// <remarks>
     604    /// Converted from Exiftool version 10.33 created by Phil Harvey
     605    /// http://www.sno.phy.queensu.ca/~phil/exiftool/
     606    /// lib\Image\ExifTool\PrintIM.pm
     607    /// </remarks>
     608    private static void ProcessPrintIM(@NotNull final PrintIMDirectory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader, final int byteCount) throws IOException
     609    {
     610        Boolean resetByteOrder = null;
     611
     612        if (byteCount == 0)
     613        {
     614            directory.addError("Empty PrintIM data");
     615            return;
     616        }
     617
     618        if (byteCount <= 15)
     619        {
     620            directory.addError("Bad PrintIM data");
     621            return;
     622        }
     623
     624        String header = reader.getString(tagValueOffset, 12, Charsets.UTF_8);
     625
     626        if (!header.startsWith("PrintIM")) //, StringComparison.Ordinal))
     627        {
     628            directory.addError("Invalid PrintIM header");
     629            return;
     630        }
     631
     632        // check size of PrintIM block
     633        int num = reader.getUInt16(tagValueOffset + 14);
     634        if (byteCount < 16 + num * 6)
     635        {
     636            // size is too big, maybe byte ordering is wrong
     637            resetByteOrder = reader.isMotorolaByteOrder();
     638            reader.setMotorolaByteOrder(!reader.isMotorolaByteOrder());
     639            num = reader.getUInt16(tagValueOffset + 14);
     640            if (byteCount < 16 + num * 6)
     641            {
     642                directory.addError("Bad PrintIM size");
     643                return;
     644            }
     645        }
     646
     647        directory.setObject(PrintIMDirectory.TagPrintImVersion, header.substring(8, 12));
     648
     649        for (int n = 0; n < num; n++)
     650        {
     651            int pos = tagValueOffset + 16 + n * 6;
     652            int tag = reader.getUInt16(pos);
     653            long val = reader.getUInt32(pos + 2);
     654
     655            directory.setObject(tag, val);
     656        }
     657
     658        if (resetByteOrder != null)
     659            reader.setMotorolaByteOrder(resetByteOrder);
     660    }
     661
    361662    private static void processKodakMakernote(@NotNull final KodakMakernoteDirectory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader)
    362663    {
     
    364665        int dataOffset = tagValueOffset + 8;
    365666        try {
    366             directory.setString(KodakMakernoteDirectory.TAG_KODAK_MODEL, reader.getString(dataOffset, 8));
     667            directory.setStringValue(KodakMakernoteDirectory.TAG_KODAK_MODEL, reader.getStringValue(dataOffset, 8, Charsets.UTF_8));
    367668            directory.setInt(KodakMakernoteDirectory.TAG_QUALITY, reader.getUInt8(dataOffset + 9));
    368669            directory.setInt(KodakMakernoteDirectory.TAG_BURST_MODE, reader.getUInt8(dataOffset + 10));
     
    394695        }
    395696    }
     697
     698    private static void processReconyxHyperFireMakernote(@NotNull final ReconyxHyperFireMakernoteDirectory directory, final int makernoteOffset, @NotNull final RandomAccessReader reader) throws IOException
     699    {
     700        directory.setObject(ReconyxHyperFireMakernoteDirectory.TAG_MAKERNOTE_VERSION, reader.getUInt16(makernoteOffset));
     701
     702        int major = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION);
     703        int minor = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION + 2);
     704        int revision = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION + 4);
     705        String buildYear = String.format("%04X", reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION + 6));
     706        String buildDate = String.format("%04X", reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION + 8));
     707        String buildYearAndDate = buildYear + buildDate;
     708        Integer build;
     709        try {
     710            build = Integer.parseInt(buildYearAndDate);
     711        } catch (NumberFormatException e) {
     712            build = null;
     713        }
     714        if (build != null)
     715        {
     716            directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION, String.format("%d.%d.%d.%s", major, minor, revision, build));
     717        }
     718        else
     719        {
     720            directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION, String.format("%d.%d.%d", major, minor, revision));
     721            directory.addError("Error processing Reconyx HyperFire makernote data: build '" + buildYearAndDate + "' is not in the expected format and will be omitted from Firmware Version.");
     722        }
     723
     724        directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_TRIGGER_MODE, String.valueOf((char)reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_TRIGGER_MODE)));
     725        directory.setIntArray(ReconyxHyperFireMakernoteDirectory.TAG_SEQUENCE,
     726                      new int[]
     727                      {
     728                          reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_SEQUENCE),
     729                          reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_SEQUENCE + 2)
     730                      });
     731
     732        int eventNumberHigh = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_EVENT_NUMBER);
     733        int eventNumberLow = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_EVENT_NUMBER + 2);
     734        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_EVENT_NUMBER, (eventNumberHigh << 16) + eventNumberLow);
     735
     736        int seconds = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL);
     737        int minutes = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 2);
     738        int hour = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 4);
     739        int month = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 6);
     740        int day = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 8);
     741        int year = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 10);
     742
     743        if ((seconds >= 0 && seconds < 60) &&
     744            (minutes >= 0 && minutes < 60) &&
     745            (hour >= 0 && hour < 24) &&
     746            (month >= 1 && month < 13) &&
     747            (day >= 1 && day < 32) &&
     748            (year >= 1 && year <= 9999))
     749        {
     750            directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL,
     751                    String.format("%4d:%2d:%2d %2d:%2d:%2d", year, month, day, hour, minutes, seconds));
     752        }
     753        else
     754        {
     755            directory.addError("Error processing Reconyx HyperFire makernote data: Date/Time Original " + year + "-" + month + "-" + day + " " + hour + ":" + minutes + ":" + seconds + " is not a valid date/time.");
     756        }
     757
     758        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_MOON_PHASE, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_MOON_PHASE));
     759        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_AMBIENT_TEMPERATURE_FAHRENHEIT, reader.getInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_AMBIENT_TEMPERATURE_FAHRENHEIT));
     760        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_AMBIENT_TEMPERATURE, reader.getInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_AMBIENT_TEMPERATURE));
     761        //directory.setByteArray(ReconyxHyperFireMakernoteDirectory.TAG_SERIAL_NUMBER, reader.getBytes(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_SERIAL_NUMBER, 28));
     762        directory.setStringValue(ReconyxHyperFireMakernoteDirectory.TAG_SERIAL_NUMBER, new StringValue(reader.getBytes(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_SERIAL_NUMBER, 28), Charsets.UTF_16LE));
     763        // two unread bytes: the serial number's terminating null
     764
     765        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_CONTRAST, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_CONTRAST));
     766        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_BRIGHTNESS, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_BRIGHTNESS));
     767        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_SHARPNESS, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_SHARPNESS));
     768        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_SATURATION, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_SATURATION));
     769        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_INFRARED_ILLUMINATOR, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_INFRARED_ILLUMINATOR));
     770        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_MOTION_SENSITIVITY, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_MOTION_SENSITIVITY));
     771        directory.setDouble(ReconyxHyperFireMakernoteDirectory.TAG_BATTERY_VOLTAGE, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_BATTERY_VOLTAGE) / 1000.0);
     772        directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_USER_LABEL, reader.getNullTerminatedString(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_USER_LABEL, 44, Charsets.UTF_8));
     773    }
     774
     775    private static void processReconyxUltraFireMakernote(@NotNull final ReconyxUltraFireMakernoteDirectory directory, final int makernoteOffset, @NotNull final RandomAccessReader reader) throws IOException
     776    {
     777        directory.setString(ReconyxUltraFireMakernoteDirectory.TAG_LABEL, reader.getString(makernoteOffset, 9, Charsets.UTF_8));
     778        /*uint makernoteID = ByteConvert.FromBigEndianToNative(reader.GetUInt32(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagMakernoteID));
     779        directory.Set(ReconyxUltraFireMakernoteDirectory.TagMakernoteID, makernoteID);
     780        if (makernoteID != ReconyxUltraFireMakernoteDirectory.MAKERNOTE_ID)
     781        {
     782            directory.addError("Error processing Reconyx UltraFire makernote data: unknown Makernote ID 0x" + makernoteID.ToString("x8"));
     783            return;
     784        }
     785        directory.Set(ReconyxUltraFireMakernoteDirectory.TagMakernoteSize, ByteConvert.FromBigEndianToNative(reader.GetUInt32(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagMakernoteSize)));
     786        uint makernotePublicID = ByteConvert.FromBigEndianToNative(reader.GetUInt32(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagMakernotePublicID));
     787        directory.Set(ReconyxUltraFireMakernoteDirectory.TagMakernotePublicID, makernotePublicID);
     788        if (makernotePublicID != ReconyxUltraFireMakernoteDirectory.MAKERNOTE_PUBLIC_ID)
     789        {
     790            directory.addError("Error processing Reconyx UltraFire makernote data: unknown Makernote Public ID 0x" + makernotePublicID.ToString("x8"));
     791            return;
     792        }*/
     793        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagMakernotePublicSize, ByteConvert.FromBigEndianToNative(reader.GetUInt16(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagMakernotePublicSize)));
     794
     795        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagCameraVersion, ProcessReconyxUltraFireVersion(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagCameraVersion, reader));
     796        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagUibVersion, ProcessReconyxUltraFireVersion(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagUibVersion, reader));
     797        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagBtlVersion, ProcessReconyxUltraFireVersion(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagBtlVersion, reader));
     798        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagPexVersion, ProcessReconyxUltraFireVersion(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagPexVersion, reader));
     799
     800        directory.setString(ReconyxUltraFireMakernoteDirectory.TAG_EVENT_TYPE, reader.getString(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_EVENT_TYPE, 1, Charsets.UTF_8));
     801        directory.setIntArray(ReconyxUltraFireMakernoteDirectory.TAG_SEQUENCE,
     802                      new int[]
     803                      {
     804                          reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_SEQUENCE),
     805                          reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_SEQUENCE + 1)
     806                      });
     807        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagEventNumber, ByteConvert.FromBigEndianToNative(reader.GetUInt32(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagEventNumber)));
     808
     809        byte seconds = reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL);
     810        byte minutes = reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 1);
     811        byte hour = reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 2);
     812        byte day = reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 3);
     813        byte month = reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 4);
     814        /*ushort year = ByteConvert.FromBigEndianToNative(reader.GetUInt16(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagDateTimeOriginal + 5));
     815        if ((seconds >= 0 && seconds < 60) &&
     816            (minutes >= 0 && minutes < 60) &&
     817            (hour >= 0 && hour < 24) &&
     818            (month >= 1 && month < 13) &&
     819            (day >= 1 && day < 32) &&
     820            (year >= 1 && year <= 9999))
     821        {
     822            directory.Set(ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL, new DateTime(year, month, day, hour, minutes, seconds, DateTimeKind.Unspecified));
     823        }
     824        else
     825        {
     826            directory.addError("Error processing Reconyx UltraFire makernote data: Date/Time Original " + year + "-" + month + "-" + day + " " + hour + ":" + minutes + ":" + seconds + " is not a valid date/time.");
     827        }*/
     828        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagDayOfWeek, reader.GetByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagDayOfWeek));
     829
     830        directory.setInt(ReconyxUltraFireMakernoteDirectory.TAG_MOON_PHASE, reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_MOON_PHASE));
     831        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagAmbientTemperatureFahrenheit, ByteConvert.FromBigEndianToNative(reader.GetInt16(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagAmbientTemperatureFahrenheit)));
     832        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagAmbientTemperature, ByteConvert.FromBigEndianToNative(reader.GetInt16(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagAmbientTemperature)));
     833
     834        directory.setInt(ReconyxUltraFireMakernoteDirectory.TAG_FLASH, reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_FLASH));
     835        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagBatteryVoltage, ByteConvert.FromBigEndianToNative(reader.GetUInt16(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagBatteryVoltage)) / 1000.0);
     836        directory.setStringValue(ReconyxUltraFireMakernoteDirectory.TAG_SERIAL_NUMBER, new StringValue(reader.getBytes(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_SERIAL_NUMBER, 14), Charsets.UTF_8));
     837        // unread byte: the serial number's terminating null
     838        directory.setString(ReconyxUltraFireMakernoteDirectory.TAG_USER_LABEL, reader.getNullTerminatedString(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_USER_LABEL, 20, Charsets.UTF_8));
     839    }
    396840}
    397841
  • trunk/src/com/drew/metadata/exif/GpsDescriptor.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3636 * @author Drew Noakes https://drewnoakes.com
    3737 */
     38@SuppressWarnings("WeakerAccess")
    3839public class GpsDescriptor extends TagDescriptor<GpsDirectory>
    3940{
  • trunk/src/com/drew/metadata/exif/GpsDirectory.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3838 * @author Drew Noakes https://drewnoakes.com
    3939 */
     40@SuppressWarnings("WeakerAccess")
    4041public class GpsDirectory extends ExifDirectoryBase
    4142{
  • trunk/src/com/drew/metadata/exif/makernotes/CanonMakernoteDescriptor.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2626
    2727import java.text.DecimalFormat;
     28import java.util.HashMap;
    2829
    2930import static com.drew.metadata.exif.makernotes.CanonMakernoteDirectory.*;
     
    3435 * @author Drew Noakes https://drewnoakes.com
    3536 */
     37@SuppressWarnings("WeakerAccess")
    3638public class CanonMakernoteDescriptor extends TagDescriptor<CanonMakernoteDirectory>
    3739{
     
    5456            case CameraSettings.TAG_DIGITAL_ZOOM:
    5557                return getDigitalZoomDescription();
     58            case CameraSettings.TAG_RECORD_MODE:
     59                return getRecordModeDescription();
    5660            case CameraSettings.TAG_QUALITY:
    5761                return getQualityDescription();
     
    102106            case FocalLength.TAG_FLASH_BIAS:
    103107                return getFlashBiasDescription();
     108            case AFInfo.TAG_AF_POINTS_IN_FOCUS:
     109                return getTagAfPointsInFocus();
     110            case CameraSettings.TAG_MAX_APERTURE:
     111                return getMaxApertureDescription();
     112            case CameraSettings.TAG_MIN_APERTURE:
     113                return getMinApertureDescription();
     114            case CameraSettings.TAG_FOCUS_CONTINUOUS:
     115                return getFocusContinuousDescription();
     116            case CameraSettings.TAG_AE_SETTING:
     117                return getAESettingDescription();
     118            case CanonMakernoteDirectory.CameraSettings.TAG_DISPLAY_APERTURE:
     119                return getDisplayApertureDescription();
     120            case CanonMakernoteDirectory.CameraSettings.TAG_SPOT_METERING_MODE:
     121                return getSpotMeteringModeDescription();
     122            case CanonMakernoteDirectory.CameraSettings.TAG_PHOTO_EFFECT:
     123                return getPhotoEffectDescription();
     124            case CanonMakernoteDirectory.CameraSettings.TAG_MANUAL_FLASH_OUTPUT:
     125                return getManualFlashOutputDescription();
     126            case CanonMakernoteDirectory.CameraSettings.TAG_COLOR_TONE:
     127                return getColorToneDescription();
     128            case CanonMakernoteDirectory.CameraSettings.TAG_SRAW_QUALITY:
     129                return getSRawQualityDescription();
    104130
    105131            // It turns out that these values are dependent upon the camera model and therefore the below code was
     
    346372        //  0, 0.33,  0.5, 0.66,  1
    347373
    348         return ((isNegative) ? "-" : "") + Float.toString(value / 32f) + " EV";
     374        return (isNegative ? "-" : "") + Float.toString(value / 32f) + " EV";
    349375    }
    350376
     
    364390            return "Unknown (" + value + ")";
    365391        }
     392    }
     393
     394    @Nullable
     395    public String getTagAfPointsInFocus()
     396    {
     397        Integer value = _directory.getInteger(AFInfo.TAG_AF_POINTS_IN_FOCUS);
     398        if (value == null)
     399            return null;
     400
     401        StringBuilder sb = new StringBuilder();
     402
     403        for (int i = 0; i < 16; i++)
     404        {
     405            if ((value & 1 << i) != 0)
     406            {
     407                if (sb.length() != 0)
     408                    sb.append(',');
     409                sb.append(i);
     410            }
     411        }
     412
     413        return sb.length() == 0 ? "None" : sb.toString();
    366414    }
    367415
     
    393441        if (value == null)
    394442            return null;
    395         if (((value >> 14) & 1) > 0) {
     443        if (((value >> 14) & 1) != 0) {
    396444            return "External E-TTL";
    397445        }
    398         if (((value >> 13) & 1) > 0) {
     446        if (((value >> 13) & 1) != 0) {
    399447            return "Internal flash";
    400448        }
    401         if (((value >> 11) & 1) > 0) {
     449        if (((value >> 11) & 1) != 0) {
    402450            return "FP sync used";
    403451        }
    404         if (((value >> 4) & 1) > 0) {
     452        if (((value >> 4) & 1) != 0) {
    405453            return "FP sync enabled";
    406454        }
     
    461509            return null;
    462510
    463         return "Lens type: " + Integer.toString(value);
     511        return _lensTypeById.containsKey(value)
     512            ? _lensTypeById.get(value)
     513            : String.format("Unknown (%d)", value);
     514    }
     515
     516    @Nullable
     517    public String getMaxApertureDescription()
     518    {
     519        Integer value = _directory.getInteger(CameraSettings.TAG_MAX_APERTURE);
     520        if (value == null)
     521            return null;
     522        if (value > 512)
     523            return String.format("Unknown (%d)", value);
     524        return getFStopDescription(Math.exp(decodeCanonEv(value) * Math.log(2.0) / 2.0));
     525    }
     526
     527    @Nullable
     528    public String getMinApertureDescription()
     529    {
     530        Integer value = _directory.getInteger(CameraSettings.TAG_MIN_APERTURE);
     531        if (value == null)
     532            return null;
     533        if (value > 512)
     534            return String.format("Unknown (%d)", value);
     535        return getFStopDescription(Math.exp(decodeCanonEv(value) * Math.log(2.0) / 2.0));
    464536    }
    465537
     
    499571        // Canon PowerShot S3 is special
    500572        int canonMask = 0x4000;
    501         if ((value & canonMask) > 0)
     573        if ((value & canonMask) != 0)
    502574            return "" + (value & ~canonMask);
    503575
     
    700772
    701773    @Nullable
     774    public String getRecordModeDescription()
     775    {
     776        return getIndexedDescription(CameraSettings.TAG_RECORD_MODE, 1, "JPEG", "CRW+THM", "AVI+THM", "TIF", "TIF+JPEG", "CR2", "CR2+JPEG", null, "MOV", "MP4");
     777    }
     778
     779    @Nullable
    702780    public String getFocusTypeDescription()
    703781    {
     
    724802        return getIndexedDescription(CameraSettings.TAG_FLASH_ACTIVITY, "Flash did not fire", "Flash fired");
    725803    }
     804
     805    @Nullable
     806    public String getFocusContinuousDescription()
     807    {
     808        return getIndexedDescription(CameraSettings.TAG_FOCUS_CONTINUOUS, 0,
     809            "Single", "Continuous", null, null, null, null, null, null, "Manual");
     810    }
     811
     812    @Nullable
     813    public String getAESettingDescription()
     814    {
     815        return getIndexedDescription(CameraSettings.TAG_AE_SETTING, 0,
     816            "Normal AE", "Exposure Compensation", "AE Lock", "AE Lock + Exposure Comp.", "No AE");
     817    }
     818
     819    @Nullable
     820    public String getDisplayApertureDescription()
     821    {
     822        Integer value = _directory.getInteger(CameraSettings.TAG_DISPLAY_APERTURE);
     823        if (value == null)
     824            return null;
     825
     826        if (value == 0xFFFF)
     827            return value.toString();
     828        return getFStopDescription(value / 10f);
     829    }
     830
     831    @Nullable
     832    public String getSpotMeteringModeDescription()
     833    {
     834        return getIndexedDescription(CanonMakernoteDirectory.CameraSettings.TAG_SPOT_METERING_MODE, 0,
     835            "Center", "AF Point");
     836    }
     837
     838    @Nullable
     839    public String getPhotoEffectDescription()
     840    {
     841        Integer value = _directory.getInteger(CameraSettings.TAG_PHOTO_EFFECT);
     842        if (value == null)
     843            return null;
     844
     845        switch (value)
     846        {
     847            case 0:
     848                return "Off";
     849            case 1:
     850                return "Vivid";
     851            case 2:
     852                return "Neutral";
     853            case 3:
     854                return "Smooth";
     855            case 4:
     856                return "Sepia";
     857            case 5:
     858                return "B&W";
     859            case 6:
     860                return "Custom";
     861            case 100:
     862                return "My Color Data";
     863            default:
     864                return "Unknown (" + value + ")";
     865        }
     866    }
     867
     868    @Nullable
     869    public String getManualFlashOutputDescription()
     870    {
     871        Integer value = _directory.getInteger(CameraSettings.TAG_MANUAL_FLASH_OUTPUT);
     872        if (value == null)
     873            return null;
     874
     875        switch (value)
     876        {
     877            case 0:
     878                return "n/a";
     879            case 0x500:
     880                return "Full";
     881            case 0x502:
     882                return "Medium";
     883            case 0x504:
     884                return "Low";
     885            case 0x7fff:
     886                return "n/a";   // (EOS models)
     887            default:
     888                return "Unknown (" + value + ")";
     889        }
     890    }
     891
     892    @Nullable
     893    public String getColorToneDescription()
     894    {
     895        Integer value = _directory.getInteger(CameraSettings.TAG_COLOR_TONE);
     896        if (value == null)
     897            return null;
     898
     899        return value == 0x7fff ? "n/a" : value.toString();
     900    }
     901
     902    @Nullable
     903    public String getSRawQualityDescription()
     904    {
     905        return getIndexedDescription(CanonMakernoteDirectory.CameraSettings.TAG_SRAW_QUALITY, 0, "n/a", "sRAW1 (mRAW)", "sRAW2 (sRAW)");
     906    }
     907
     908    /**
     909     * Canon hex-based EV (modulo 0x20) to real number.
     910     *
     911     * Converted from Exiftool version 10.10 created by Phil Harvey
     912     * http://www.sno.phy.queensu.ca/~phil/exiftool/
     913     * lib\Image\ExifTool\Canon.pm
     914     *
     915     *         eg) 0x00 -> 0
     916     *             0x0c -> 0.33333
     917     *             0x10 -> 0.5
     918     *             0x14 -> 0.66666
     919     *             0x20 -> 1   ... etc
     920     */
     921    private double decodeCanonEv(int val)
     922    {
     923        int sign = 1;
     924        if (val < 0)
     925        {
     926            val = -val;
     927            sign = -1;
     928        }
     929
     930        int frac = val & 0x1f;
     931        val -= frac;
     932
     933        if (frac == 0x0c)
     934            frac = 0x20 / 3;
     935        else if (frac == 0x14)
     936            frac = 0x40 / 3;
     937
     938        return sign * (val + frac) / (double)0x20;
     939    }
     940
     941    /**
     942     *  Map from <see cref="CanonMakernoteDirectory.CameraSettings.TagLensType"/> to string descriptions.
     943     *
     944     *  Data sourced from http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Canon.html#LensType
     945     *
     946     *  Note that only Canon lenses are listed. Lenses from other manufacturers may identify themselves to the camera
     947     *  as being from this set, but in fact may be quite different. This limits the usefulness of this data,
     948     *  unfortunately.
     949     */
     950    private static final HashMap<Integer, String> _lensTypeById = new HashMap<Integer, String>();
     951
     952    static {
     953        _lensTypeById.put(1, "Canon EF 50mm f/1.8");
     954        _lensTypeById.put(2, "Canon EF 28mm f/2.8");
     955        _lensTypeById.put(3, "Canon EF 135mm f/2.8 Soft");
     956        _lensTypeById.put(4, "Canon EF 35-105mm f/3.5-4.5 or Sigma Lens");
     957        _lensTypeById.put(5, "Canon EF 35-70mm f/3.5-4.5");
     958        _lensTypeById.put(6, "Canon EF 28-70mm f/3.5-4.5 or Sigma or Tokina Lens");
     959        _lensTypeById.put(7, "Canon EF 100-300mm f/5.6L");
     960        _lensTypeById.put(8, "Canon EF 100-300mm f/5.6 or Sigma or Tokina Lens");
     961        _lensTypeById.put(9, "Canon EF 70-210mm f/4");
     962        _lensTypeById.put(10, "Canon EF 50mm f/2.5 Macro or Sigma Lens");
     963        _lensTypeById.put(11, "Canon EF 35mm f/2");
     964        _lensTypeById.put(13, "Canon EF 15mm f/2.8 Fisheye");
     965        _lensTypeById.put(14, "Canon EF 50-200mm f/3.5-4.5L");
     966        _lensTypeById.put(15, "Canon EF 50-200mm f/3.5-4.5");
     967        _lensTypeById.put(16, "Canon EF 35-135mm f/3.5-4.5");
     968        _lensTypeById.put(17, "Canon EF 35-70mm f/3.5-4.5A");
     969        _lensTypeById.put(18, "Canon EF 28-70mm f/3.5-4.5");
     970        _lensTypeById.put(20, "Canon EF 100-200mm f/4.5A");
     971        _lensTypeById.put(21, "Canon EF 80-200mm f/2.8L");
     972        _lensTypeById.put(22, "Canon EF 20-35mm f/2.8L or Tokina Lens");
     973        _lensTypeById.put(23, "Canon EF 35-105mm f/3.5-4.5");
     974        _lensTypeById.put(24, "Canon EF 35-80mm f/4-5.6 Power Zoom");
     975        _lensTypeById.put(25, "Canon EF 35-80mm f/4-5.6 Power Zoom");
     976        _lensTypeById.put(26, "Canon EF 100mm f/2.8 Macro or Other Lens");
     977        _lensTypeById.put(27, "Canon EF 35-80mm f/4-5.6");
     978        _lensTypeById.put(28, "Canon EF 80-200mm f/4.5-5.6 or Tamron Lens");
     979        _lensTypeById.put(29, "Canon EF 50mm f/1.8 II");
     980        _lensTypeById.put(30, "Canon EF 35-105mm f/4.5-5.6");
     981        _lensTypeById.put(31, "Canon EF 75-300mm f/4-5.6 or Tamron Lens");
     982        _lensTypeById.put(32, "Canon EF 24mm f/2.8 or Sigma Lens");
     983        _lensTypeById.put(33, "Voigtlander or Carl Zeiss Lens");
     984        _lensTypeById.put(35, "Canon EF 35-80mm f/4-5.6");
     985        _lensTypeById.put(36, "Canon EF 38-76mm f/4.5-5.6");
     986        _lensTypeById.put(37, "Canon EF 35-80mm f/4-5.6 or Tamron Lens");
     987        _lensTypeById.put(38, "Canon EF 80-200mm f/4.5-5.6");
     988        _lensTypeById.put(39, "Canon EF 75-300mm f/4-5.6");
     989        _lensTypeById.put(40, "Canon EF 28-80mm f/3.5-5.6");
     990        _lensTypeById.put(41, "Canon EF 28-90mm f/4-5.6");
     991        _lensTypeById.put(42, "Canon EF 28-200mm f/3.5-5.6 or Tamron Lens");
     992        _lensTypeById.put(43, "Canon EF 28-105mm f/4-5.6");
     993        _lensTypeById.put(44, "Canon EF 90-300mm f/4.5-5.6");
     994        _lensTypeById.put(45, "Canon EF-S 18-55mm f/3.5-5.6 [II]");
     995        _lensTypeById.put(46, "Canon EF 28-90mm f/4-5.6");
     996        _lensTypeById.put(47, "Zeiss Milvus 35mm f/2 or 50mm f/2");
     997        _lensTypeById.put(48, "Canon EF-S 18-55mm f/3.5-5.6 IS");
     998        _lensTypeById.put(49, "Canon EF-S 55-250mm f/4-5.6 IS");
     999        _lensTypeById.put(50, "Canon EF-S 18-200mm f/3.5-5.6 IS");
     1000        _lensTypeById.put(51, "Canon EF-S 18-135mm f/3.5-5.6 IS");
     1001        _lensTypeById.put(52, "Canon EF-S 18-55mm f/3.5-5.6 IS II");
     1002        _lensTypeById.put(53, "Canon EF-S 18-55mm f/3.5-5.6 III");
     1003        _lensTypeById.put(54, "Canon EF-S 55-250mm f/4-5.6 IS II");
     1004        _lensTypeById.put(94, "Canon TS-E 17mm f/4L");
     1005        _lensTypeById.put(95, "Canon TS-E 24.0mm f/3.5 L II");
     1006        _lensTypeById.put(124, "Canon MP-E 65mm f/2.8 1-5x Macro Photo");
     1007        _lensTypeById.put(125, "Canon TS-E 24mm f/3.5L");
     1008        _lensTypeById.put(126, "Canon TS-E 45mm f/2.8");
     1009        _lensTypeById.put(127, "Canon TS-E 90mm f/2.8");
     1010        _lensTypeById.put(129, "Canon EF 300mm f/2.8L");
     1011        _lensTypeById.put(130, "Canon EF 50mm f/1.0L");
     1012        _lensTypeById.put(131, "Canon EF 28-80mm f/2.8-4L or Sigma Lens");
     1013        _lensTypeById.put(132, "Canon EF 1200mm f/5.6L");
     1014        _lensTypeById.put(134, "Canon EF 600mm f/4L IS");
     1015        _lensTypeById.put(135, "Canon EF 200mm f/1.8L");
     1016        _lensTypeById.put(136, "Canon EF 300mm f/2.8L");
     1017        _lensTypeById.put(137, "Canon EF 85mm f/1.2L or Sigma or Tamron Lens");
     1018        _lensTypeById.put(138, "Canon EF 28-80mm f/2.8-4L");
     1019        _lensTypeById.put(139, "Canon EF 400mm f/2.8L");
     1020        _lensTypeById.put(140, "Canon EF 500mm f/4.5L");
     1021        _lensTypeById.put(141, "Canon EF 500mm f/4.5L");
     1022        _lensTypeById.put(142, "Canon EF 300mm f/2.8L IS");
     1023        _lensTypeById.put(143, "Canon EF 500mm f/4L IS or Sigma Lens");
     1024        _lensTypeById.put(144, "Canon EF 35-135mm f/4-5.6 USM");
     1025        _lensTypeById.put(145, "Canon EF 100-300mm f/4.5-5.6 USM");
     1026        _lensTypeById.put(146, "Canon EF 70-210mm f/3.5-4.5 USM");
     1027        _lensTypeById.put(147, "Canon EF 35-135mm f/4-5.6 USM");
     1028        _lensTypeById.put(148, "Canon EF 28-80mm f/3.5-5.6 USM");
     1029        _lensTypeById.put(149, "Canon EF 100mm f/2 USM");
     1030        _lensTypeById.put(150, "Canon EF 14mm f/2.8L or Sigma Lens");
     1031        _lensTypeById.put(151, "Canon EF 200mm f/2.8L");
     1032        _lensTypeById.put(152, "Canon EF 300mm f/4L IS or Sigma Lens");
     1033        _lensTypeById.put(153, "Canon EF 35-350mm f/3.5-5.6L or Sigma or Tamron Lens");
     1034        _lensTypeById.put(154, "Canon EF 20mm f/2.8 USM or Zeiss Lens");
     1035        _lensTypeById.put(155, "Canon EF 85mm f/1.8 USM");
     1036        _lensTypeById.put(156, "Canon EF 28-105mm f/3.5-4.5 USM or Tamron Lens");
     1037        _lensTypeById.put(160, "Canon EF 20-35mm f/3.5-4.5 USM or Tamron or Tokina Lens");
     1038        _lensTypeById.put(161, "Canon EF 28-70mm f/2.8L or Sigma or Tamron Lens");
     1039        _lensTypeById.put(162, "Canon EF 200mm f/2.8L");
     1040        _lensTypeById.put(163, "Canon EF 300mm f/4L");
     1041        _lensTypeById.put(164, "Canon EF 400mm f/5.6L");
     1042        _lensTypeById.put(165, "Canon EF 70-200mm f/2.8 L");
     1043        _lensTypeById.put(166, "Canon EF 70-200mm f/2.8 L + 1.4x");
     1044        _lensTypeById.put(167, "Canon EF 70-200mm f/2.8 L + 2x");
     1045        _lensTypeById.put(168, "Canon EF 28mm f/1.8 USM or Sigma Lens");
     1046        _lensTypeById.put(169, "Canon EF 17-35mm f/2.8L or Sigma Lens");
     1047        _lensTypeById.put(170, "Canon EF 200mm f/2.8L II");
     1048        _lensTypeById.put(171, "Canon EF 300mm f/4L");
     1049        _lensTypeById.put(172, "Canon EF 400mm f/5.6L or Sigma Lens");
     1050        _lensTypeById.put(173, "Canon EF 180mm Macro f/3.5L or Sigma Lens");
     1051        _lensTypeById.put(174, "Canon EF 135mm f/2L or Other Lens");
     1052        _lensTypeById.put(175, "Canon EF 400mm f/2.8L");
     1053        _lensTypeById.put(176, "Canon EF 24-85mm f/3.5-4.5 USM");
     1054        _lensTypeById.put(177, "Canon EF 300mm f/4L IS");
     1055        _lensTypeById.put(178, "Canon EF 28-135mm f/3.5-5.6 IS");
     1056        _lensTypeById.put(179, "Canon EF 24mm f/1.4L");
     1057        _lensTypeById.put(180, "Canon EF 35mm f/1.4L or Other Lens");
     1058        _lensTypeById.put(181, "Canon EF 100-400mm f/4.5-5.6L IS + 1.4x or Sigma Lens");
     1059        _lensTypeById.put(182, "Canon EF 100-400mm f/4.5-5.6L IS + 2x or Sigma Lens");
     1060        _lensTypeById.put(183, "Canon EF 100-400mm f/4.5-5.6L IS or Sigma Lens");
     1061        _lensTypeById.put(184, "Canon EF 400mm f/2.8L + 2x");
     1062        _lensTypeById.put(185, "Canon EF 600mm f/4L IS");
     1063        _lensTypeById.put(186, "Canon EF 70-200mm f/4L");
     1064        _lensTypeById.put(187, "Canon EF 70-200mm f/4L + 1.4x");
     1065        _lensTypeById.put(188, "Canon EF 70-200mm f/4L + 2x");
     1066        _lensTypeById.put(189, "Canon EF 70-200mm f/4L + 2.8x");
     1067        _lensTypeById.put(190, "Canon EF 100mm f/2.8 Macro USM");
     1068        _lensTypeById.put(191, "Canon EF 400mm f/4 DO IS");
     1069        _lensTypeById.put(193, "Canon EF 35-80mm f/4-5.6 USM");
     1070        _lensTypeById.put(194, "Canon EF 80-200mm f/4.5-5.6 USM");
     1071        _lensTypeById.put(195, "Canon EF 35-105mm f/4.5-5.6 USM");
     1072        _lensTypeById.put(196, "Canon EF 75-300mm f/4-5.6 USM");
     1073        _lensTypeById.put(197, "Canon EF 75-300mm f/4-5.6 IS USM");
     1074        _lensTypeById.put(198, "Canon EF 50mm f/1.4 USM or Zeiss Lens");
     1075        _lensTypeById.put(199, "Canon EF 28-80mm f/3.5-5.6 USM");
     1076        _lensTypeById.put(200, "Canon EF 75-300mm f/4-5.6 USM");
     1077        _lensTypeById.put(201, "Canon EF 28-80mm f/3.5-5.6 USM");
     1078        _lensTypeById.put(202, "Canon EF 28-80mm f/3.5-5.6 USM IV");
     1079        _lensTypeById.put(208, "Canon EF 22-55mm f/4-5.6 USM");
     1080        _lensTypeById.put(209, "Canon EF 55-200mm f/4.5-5.6");
     1081        _lensTypeById.put(210, "Canon EF 28-90mm f/4-5.6 USM");
     1082        _lensTypeById.put(211, "Canon EF 28-200mm f/3.5-5.6 USM");
     1083        _lensTypeById.put(212, "Canon EF 28-105mm f/4-5.6 USM");
     1084        _lensTypeById.put(213, "Canon EF 90-300mm f/4.5-5.6 USM or Tamron Lens");
     1085        _lensTypeById.put(214, "Canon EF-S 18-55mm f/3.5-5.6 USM");
     1086        _lensTypeById.put(215, "Canon EF 55-200mm f/4.5-5.6 II USM");
     1087        _lensTypeById.put(217, "Tamron AF 18-270mm f/3.5-6.3 Di II VC PZD");
     1088        _lensTypeById.put(224, "Canon EF 70-200mm f/2.8L IS");
     1089        _lensTypeById.put(225, "Canon EF 70-200mm f/2.8L IS + 1.4x");
     1090        _lensTypeById.put(226, "Canon EF 70-200mm f/2.8L IS + 2x");
     1091        _lensTypeById.put(227, "Canon EF 70-200mm f/2.8L IS + 2.8x");
     1092        _lensTypeById.put(228, "Canon EF 28-105mm f/3.5-4.5 USM");
     1093        _lensTypeById.put(229, "Canon EF 16-35mm f/2.8L");
     1094        _lensTypeById.put(230, "Canon EF 24-70mm f/2.8L");
     1095        _lensTypeById.put(231, "Canon EF 17-40mm f/4L");
     1096        _lensTypeById.put(232, "Canon EF 70-300mm f/4.5-5.6 DO IS USM");
     1097        _lensTypeById.put(233, "Canon EF 28-300mm f/3.5-5.6L IS");
     1098        _lensTypeById.put(234, "Canon EF-S 17-85mm f/4-5.6 IS USM or Tokina Lens");
     1099        _lensTypeById.put(235, "Canon EF-S 10-22mm f/3.5-4.5 USM");
     1100        _lensTypeById.put(236, "Canon EF-S 60mm f/2.8 Macro USM");
     1101        _lensTypeById.put(237, "Canon EF 24-105mm f/4L IS");
     1102        _lensTypeById.put(238, "Canon EF 70-300mm f/4-5.6 IS USM");
     1103        _lensTypeById.put(239, "Canon EF 85mm f/1.2L II");
     1104        _lensTypeById.put(240, "Canon EF-S 17-55mm f/2.8 IS USM");
     1105        _lensTypeById.put(241, "Canon EF 50mm f/1.2L");
     1106        _lensTypeById.put(242, "Canon EF 70-200mm f/4L IS");
     1107        _lensTypeById.put(243, "Canon EF 70-200mm f/4L IS + 1.4x");
     1108        _lensTypeById.put(244, "Canon EF 70-200mm f/4L IS + 2x");
     1109        _lensTypeById.put(245, "Canon EF 70-200mm f/4L IS + 2.8x");
     1110        _lensTypeById.put(246, "Canon EF 16-35mm f/2.8L II");
     1111        _lensTypeById.put(247, "Canon EF 14mm f/2.8L II USM");
     1112        _lensTypeById.put(248, "Canon EF 200mm f/2L IS or Sigma Lens");
     1113        _lensTypeById.put(249, "Canon EF 800mm f/5.6L IS");
     1114        _lensTypeById.put(250, "Canon EF 24mm f/1.4L II or Sigma Lens");
     1115        _lensTypeById.put(251, "Canon EF 70-200mm f/2.8L IS II USM");
     1116        _lensTypeById.put(252, "Canon EF 70-200mm f/2.8L IS II USM + 1.4x");
     1117        _lensTypeById.put(253, "Canon EF 70-200mm f/2.8L IS II USM + 2x");
     1118        _lensTypeById.put(254, "Canon EF 100mm f/2.8L Macro IS USM");
     1119        _lensTypeById.put(255, "Sigma 24-105mm f/4 DG OS HSM | A or Other Sigma Lens");
     1120        _lensTypeById.put(488, "Canon EF-S 15-85mm f/3.5-5.6 IS USM");
     1121        _lensTypeById.put(489, "Canon EF 70-300mm f/4-5.6L IS USM");
     1122        _lensTypeById.put(490, "Canon EF 8-15mm f/4L Fisheye USM");
     1123        _lensTypeById.put(491, "Canon EF 300mm f/2.8L IS II USM");
     1124        _lensTypeById.put(492, "Canon EF 400mm f/2.8L IS II USM");
     1125        _lensTypeById.put(493, "Canon EF 500mm f/4L IS II USM or EF 24-105mm f4L IS USM");
     1126        _lensTypeById.put(494, "Canon EF 600mm f/4.0L IS II USM");
     1127        _lensTypeById.put(495, "Canon EF 24-70mm f/2.8L II USM");
     1128        _lensTypeById.put(496, "Canon EF 200-400mm f/4L IS USM");
     1129        _lensTypeById.put(499, "Canon EF 200-400mm f/4L IS USM + 1.4x");
     1130        _lensTypeById.put(502, "Canon EF 28mm f/2.8 IS USM");
     1131        _lensTypeById.put(503, "Canon EF 24mm f/2.8 IS USM");
     1132        _lensTypeById.put(504, "Canon EF 24-70mm f/4L IS USM");
     1133        _lensTypeById.put(505, "Canon EF 35mm f/2 IS USM");
     1134        _lensTypeById.put(506, "Canon EF 400mm f/4 DO IS II USM");
     1135        _lensTypeById.put(507, "Canon EF 16-35mm f/4L IS USM");
     1136        _lensTypeById.put(508, "Canon EF 11-24mm f/4L USM");
     1137        _lensTypeById.put(747, "Canon EF 100-400mm f/4.5-5.6L IS II USM");
     1138        _lensTypeById.put(750, "Canon EF 35mm f/1.4L II USM");
     1139        _lensTypeById.put(4142, "Canon EF-S 18-135mm f/3.5-5.6 IS STM");
     1140        _lensTypeById.put(4143, "Canon EF-M 18-55mm f/3.5-5.6 IS STM or Tamron Lens");
     1141        _lensTypeById.put(4144, "Canon EF 40mm f/2.8 STM");
     1142        _lensTypeById.put(4145, "Canon EF-M 22mm f/2 STM");
     1143        _lensTypeById.put(4146, "Canon EF-S 18-55mm f/3.5-5.6 IS STM");
     1144        _lensTypeById.put(4147, "Canon EF-M 11-22mm f/4-5.6 IS STM");
     1145        _lensTypeById.put(4148, "Canon EF-S 55-250mm f/4-5.6 IS STM");
     1146        _lensTypeById.put(4149, "Canon EF-M 55-200mm f/4.5-6.3 IS STM");
     1147        _lensTypeById.put(4150, "Canon EF-S 10-18mm f/4.5-5.6 IS STM");
     1148        _lensTypeById.put(4152, "Canon EF 24-105mm f/3.5-5.6 IS STM");
     1149        _lensTypeById.put(4153, "Canon EF-M 15-45mm f/3.5-6.3 IS STM");
     1150        _lensTypeById.put(4154, "Canon EF-S 24mm f/2.8 STM");
     1151        _lensTypeById.put(4156, "Canon EF 50mm f/1.8 STM");
     1152        _lensTypeById.put(36912, "Canon EF-S 18-135mm f/3.5-5.6 IS USM");
     1153        _lensTypeById.put(65535, "N/A");
     1154    }
    7261155}
  • trunk/src/com/drew/metadata/exif/makernotes/CanonMakernoteDirectory.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3535 * @author Drew Noakes https://drewnoakes.com
    3636 */
     37@SuppressWarnings("WeakerAccess")
    3738public class CanonMakernoteDirectory extends Directory
    3839{
     
    159160        public static final int TAG_FOCUS_MODE_1 = OFFSET + 0x07;
    160161        public static final int TAG_UNKNOWN_3 = OFFSET + 0x08;
    161         public static final int TAG_UNKNOWN_4 = OFFSET + 0x09;
     162        public static final int TAG_RECORD_MODE = OFFSET + 0x09;
    162163        /**
    163164         * 0 = Large
     
    249250        public static final int TAG_SHORT_FOCAL_LENGTH = OFFSET + 0x18;
    250251        public static final int TAG_FOCAL_UNITS_PER_MM = OFFSET + 0x19;
    251         public static final int TAG_UNKNOWN_9 = OFFSET + 0x1A;
    252         public static final int TAG_UNKNOWN_10 = OFFSET + 0x1B;
     252        public static final int TAG_MAX_APERTURE = OFFSET + 0x1A;
     253        public static final int TAG_MIN_APERTURE = OFFSET + 0x1B;
    253254        /**
    254255         * 0 = Flash Did Not Fire
     
    257258        public static final int TAG_FLASH_ACTIVITY = OFFSET + 0x1C;
    258259        public static final int TAG_FLASH_DETAILS = OFFSET + 0x1D;
    259         public static final int TAG_UNKNOWN_12 = OFFSET + 0x1E;
    260         public static final int TAG_UNKNOWN_13 = OFFSET + 0x1F;
     260        public static final int TAG_FOCUS_CONTINUOUS = OFFSET + 0x1E;
     261        public static final int TAG_AE_SETTING = OFFSET + 0x1F;
    261262        /**
    262263         * 0 = Focus Mode: Single
     
    264265         */
    265266        public static final int TAG_FOCUS_MODE_2 = OFFSET + 0x20;
     267
     268        public static final int TAG_DISPLAY_APERTURE = OFFSET + 0x21;
     269        public static final int TAG_ZOOM_SOURCE_WIDTH = OFFSET + 0x22;
     270        public static final int TAG_ZOOM_TARGET_WIDTH = OFFSET + 0x23;
     271
     272        public static final int TAG_SPOT_METERING_MODE = OFFSET + 0x25;
     273        public static final int TAG_PHOTO_EFFECT = OFFSET + 0x26;
     274        public static final int TAG_MANUAL_FLASH_OUTPUT = OFFSET + 0x27;
     275
     276        public static final int TAG_COLOR_TONE = OFFSET + 0x29;
     277        public static final int TAG_SRAW_QUALITY = OFFSET + 0x2D;
    266278    }
    267279
     
    515527        _tagNameMap.put(CameraSettings.TAG_UNKNOWN_2, "Unknown Camera Setting 2");
    516528        _tagNameMap.put(CameraSettings.TAG_UNKNOWN_3, "Unknown Camera Setting 3");
    517         _tagNameMap.put(CameraSettings.TAG_UNKNOWN_4, "Unknown Camera Setting 4");
     529        _tagNameMap.put(CameraSettings.TAG_RECORD_MODE, "Record Mode");
    518530        _tagNameMap.put(CameraSettings.TAG_DIGITAL_ZOOM, "Digital Zoom");
    519531        _tagNameMap.put(CameraSettings.TAG_FOCUS_TYPE, "Focus Type");
    520532        _tagNameMap.put(CameraSettings.TAG_UNKNOWN_7, "Unknown Camera Setting 7");
    521533        _tagNameMap.put(CameraSettings.TAG_LENS_TYPE, "Lens Type");
    522         _tagNameMap.put(CameraSettings.TAG_UNKNOWN_9, "Unknown Camera Setting 9");
    523         _tagNameMap.put(CameraSettings.TAG_UNKNOWN_10, "Unknown Camera Setting 10");
     534        _tagNameMap.put(CameraSettings.TAG_MAX_APERTURE, "Max Aperture");
     535        _tagNameMap.put(CameraSettings.TAG_MIN_APERTURE, "Min Aperture");
    524536        _tagNameMap.put(CameraSettings.TAG_FLASH_ACTIVITY, "Flash Activity");
    525         _tagNameMap.put(CameraSettings.TAG_UNKNOWN_12, "Unknown Camera Setting 12");
    526         _tagNameMap.put(CameraSettings.TAG_UNKNOWN_13, "Unknown Camera Setting 13");
     537        _tagNameMap.put(CameraSettings.TAG_FOCUS_CONTINUOUS, "Focus Continuous");
     538        _tagNameMap.put(CameraSettings.TAG_AE_SETTING, "AE Setting");
     539        _tagNameMap.put(CameraSettings.TAG_DISPLAY_APERTURE, "Display Aperture");
     540        _tagNameMap.put(CameraSettings.TAG_ZOOM_SOURCE_WIDTH, "Zoom Source Width");
     541        _tagNameMap.put(CameraSettings.TAG_ZOOM_TARGET_WIDTH, "Zoom Target Width");
     542        _tagNameMap.put(CameraSettings.TAG_SPOT_METERING_MODE, "Spot Metering Mode");
     543        _tagNameMap.put(CameraSettings.TAG_PHOTO_EFFECT, "Photo Effect");
     544        _tagNameMap.put(CameraSettings.TAG_MANUAL_FLASH_OUTPUT, "Manual Flash Output");
     545        _tagNameMap.put(CameraSettings.TAG_COLOR_TONE, "Color Tone");
     546        _tagNameMap.put(CameraSettings.TAG_SRAW_QUALITY, "SRAW Quality");
    527547
    528548        _tagNameMap.put(FocalLength.TAG_WHITE_BALANCE, "White Balance");
     
    576596        _tagNameMap.put(AFInfo.TAG_AF_AREA_X_POSITIONS, "AF Area X Positions");
    577597        _tagNameMap.put(AFInfo.TAG_AF_AREA_Y_POSITIONS, "AF Area Y Positions");
    578         _tagNameMap.put(AFInfo.TAG_AF_POINTS_IN_FOCUS, "AF Points in Focus Count");
     598        _tagNameMap.put(AFInfo.TAG_AF_POINTS_IN_FOCUS, "AF Points in Focus");
    579599        _tagNameMap.put(AFInfo.TAG_PRIMARY_AF_POINT_1, "Primary AF Point 1");
    580600        _tagNameMap.put(AFInfo.TAG_PRIMARY_AF_POINT_2, "Primary AF Point 2");
     
    672692        // TODO is there some way to drop out 'null' or 'zero' values that are present in the array to reduce the noise?
    673693
     694        if (!(array instanceof int[])) {
     695            // no special handling...
     696            super.setObjectArray(tagType, array);
     697            return;
     698        }
     699
    674700        // Certain Canon tags contain arrays of values that we split into 'fake' tags as each
    675701        // index in the array has its own meaning and decoding.
     
    709735//                break;
    710736            case TAG_AF_INFO_ARRAY: {
    711                 int[] ints = (int[])array;
    712                 for (int i = 0; i < ints.length; i++)
    713                     setInt(AFInfo.OFFSET + i, ints[i]);
     737                // Notes from Exiftool 10.10 by Phil Harvey, lib\Image\Exiftool\Canon.pm:
     738                // Auto-focus information used by many older Canon models. The values in this
     739                // record are sequential, and some have variable sizes based on the value of
     740                // numafpoints (which may be 1,5,7,9,15,45, or 53). The AFArea coordinates are
     741                // given in a system where the image has dimensions given by AFImageWidth and
     742                // AFImageHeight, and 0,0 is the image center. The direction of the Y axis
     743                // depends on the camera model, with positive Y upwards for EOS models, but
     744                // apparently downwards for PowerShot models.
     745
     746                // AFInfo is another array with 'fake' tags. The first int of the array contains
     747                // the number of AF points. Iterate through the array one byte at a time, generally
     748                // assuming one byte corresponds to one tag UNLESS certain tag numbers are encountered.
     749                // For these, read specific subsequent bytes from the array based on the tag type. The
     750                // number of bytes read can vary.
     751
     752                int[] values = (int[])array;
     753                int numafpoints = values[0];
     754                int tagnumber = 0;
     755                for (int i = 0; i < values.length; i++)
     756                {
     757                    // These two tags store 'numafpoints' bytes of data in the array
     758                    if (AFInfo.OFFSET + tagnumber == AFInfo.TAG_AF_AREA_X_POSITIONS ||
     759                        AFInfo.OFFSET + tagnumber == AFInfo.TAG_AF_AREA_Y_POSITIONS)
     760                    {
     761                        // There could be incorrect data in the array, so boundary check
     762                        if (values.length - 1 >= (i + numafpoints))
     763                        {
     764                            short[] areaPositions = new short[numafpoints];
     765                            for (int j = 0; j < areaPositions.length; j++)
     766                                areaPositions[j] = (short)values[i + j];
     767
     768                            super.setObjectArray(AFInfo.OFFSET + tagnumber, areaPositions);
     769                        }
     770                        i += numafpoints - 1;   // assume these bytes are processed and skip
     771                    }
     772                    else if (AFInfo.OFFSET + tagnumber == AFInfo.TAG_AF_POINTS_IN_FOCUS)
     773                    {
     774                        short[] pointsInFocus = new short[((numafpoints + 15) / 16)];
     775
     776                        // There could be incorrect data in the array, so boundary check
     777                        if (values.length - 1 >= (i + pointsInFocus.length))
     778                        {
     779                            for (int j = 0; j < pointsInFocus.length; j++)
     780                                pointsInFocus[j] = (short)values[i + j];
     781
     782                            super.setObjectArray(AFInfo.OFFSET + tagnumber, pointsInFocus);
     783                        }
     784                        i += pointsInFocus.length - 1;  // assume these bytes are processed and skip
     785                    }
     786                    else
     787                        super.setObjectArray(AFInfo.OFFSET + tagnumber, values[i]);
     788                    tagnumber++;
     789                }
    714790                break;
    715791            }
  • trunk/src/com/drew/metadata/exif/makernotes/CasioType1MakernoteDescriptor.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3232 * @author Drew Noakes https://drewnoakes.com
    3333 */
     34@SuppressWarnings("WeakerAccess")
    3435public class CasioType1MakernoteDescriptor extends TagDescriptor<CasioType1MakernoteDirectory>
    3536{
  • trunk/src/com/drew/metadata/exif/makernotes/CasioType1MakernoteDirectory.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3434 * @author Drew Noakes https://drewnoakes.com
    3535 */
     36@SuppressWarnings("WeakerAccess")
    3637public class CasioType1MakernoteDirectory extends Directory
    3738{
  • trunk/src/com/drew/metadata/exif/makernotes/CasioType2MakernoteDescriptor.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3232 * @author Drew Noakes https://drewnoakes.com
    3333 */
     34@SuppressWarnings("WeakerAccess")
    3435public class CasioType2MakernoteDescriptor extends TagDescriptor<CasioType2MakernoteDirectory>
    3536{
     
    6869            case TAG_SHARPNESS:
    6970                return getSharpnessDescription();
    70             case TAG_PRINT_IMAGE_MATCHING_INFO:
    71                 return getPrintImageMatchingInfoDescription();
    7271            case TAG_PREVIEW_THUMBNAIL:
    7372                return getCasioPreviewThumbnailDescription();
     
    212211
    213212    @Nullable
    214     public String getPrintImageMatchingInfoDescription()
    215     {
    216         // TODO research PIM specification http://www.ozhiker.com/electronics/pjmt/jpeg_info/pim.html
    217         return _directory.getString(TAG_PRINT_IMAGE_MATCHING_INFO);
    218     }
    219 
    220     @Nullable
    221213    public String getSharpnessDescription()
    222214    {
  • trunk/src/com/drew/metadata/exif/makernotes/CasioType2MakernoteDirectory.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3434 * @author Drew Noakes https://drewnoakes.com
    3535 */
     36@SuppressWarnings("WeakerAccess")
    3637public class CasioType2MakernoteDirectory extends Directory
    3738{
  • trunk/src/com/drew/metadata/exif/makernotes/FujifilmMakernoteDescriptor.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    4949 * @author Drew Noakes https://drewnoakes.com
    5050 */
     51@SuppressWarnings("WeakerAccess")
    5152public class FujifilmMakernoteDescriptor extends TagDescriptor<FujifilmMakernoteDirectory>
    5253{
  • trunk/src/com/drew/metadata/exif/makernotes/FujifilmMakernoteDirectory.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3131 * @author Drew Noakes https://drewnoakes.com
    3232 */
     33@SuppressWarnings("WeakerAccess")
    3334public class FujifilmMakernoteDirectory extends Directory
    3435{
  • trunk/src/com/drew/metadata/exif/makernotes/KodakMakernoteDescriptor.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3232 * @author Drew Noakes https://drewnoakes.com
    3333 */
     34@SuppressWarnings("WeakerAccess")
    3435public class KodakMakernoteDescriptor extends TagDescriptor<KodakMakernoteDirectory>
    3536{
  • trunk/src/com/drew/metadata/exif/makernotes/KodakMakernoteDirectory.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3131 * @author Drew Noakes https://drewnoakes.com
    3232 */
     33@SuppressWarnings("WeakerAccess")
    3334public class KodakMakernoteDirectory extends Directory
    3435{
  • trunk/src/com/drew/metadata/exif/makernotes/KyoceraMakernoteDescriptor.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3939 * @author Drew Noakes https://drewnoakes.com
    4040 */
     41@SuppressWarnings("WeakerAccess")
    4142public class KyoceraMakernoteDescriptor extends TagDescriptor<KyoceraMakernoteDirectory>
    4243{
     
    5152    {
    5253        switch (tagType) {
    53             case TAG_PRINT_IMAGE_MATCHING_INFO:
    54                 return getPrintImageMatchingInfoDescription();
    5554            case TAG_PROPRIETARY_THUMBNAIL:
    5655                return getProprietaryThumbnailDataDescription();
     
    6160
    6261    @Nullable
    63     public String getPrintImageMatchingInfoDescription()
    64     {
    65         return getByteLengthDescription(TAG_PRINT_IMAGE_MATCHING_INFO);
    66     }
    67 
    68     @Nullable
    6962    public String getProprietaryThumbnailDataDescription()
    7063    {
  • trunk/src/com/drew/metadata/exif/makernotes/KyoceraMakernoteDirectory.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3131 * @author Drew Noakes https://drewnoakes.com
    3232 */
     33@SuppressWarnings("WeakerAccess")
    3334public class KyoceraMakernoteDirectory extends Directory
    3435{
  • trunk/src/com/drew/metadata/exif/makernotes/LeicaMakernoteDescriptor.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3434 * @author Drew Noakes https://drewnoakes.com
    3535 */
     36@SuppressWarnings("WeakerAccess")
    3637public class LeicaMakernoteDescriptor extends TagDescriptor<LeicaMakernoteDirectory>
    3738{
  • trunk/src/com/drew/metadata/exif/makernotes/LeicaMakernoteDirectory.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3333 * @author Drew Noakes https://drewnoakes.com
    3434 */
     35@SuppressWarnings("WeakerAccess")
    3536public class LeicaMakernoteDirectory extends Directory
    3637{
  • trunk/src/com/drew/metadata/exif/makernotes/NikonType1MakernoteDescriptor.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    4444 * @author Drew Noakes https://drewnoakes.com
    4545 */
     46@SuppressWarnings("WeakerAccess")
    4647public class NikonType1MakernoteDescriptor extends TagDescriptor<NikonType1MakernoteDirectory>
    4748{
  • trunk/src/com/drew/metadata/exif/makernotes/NikonType1MakernoteDirectory.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    4040 * @author Drew Noakes https://drewnoakes.com
    4141 */
     42@SuppressWarnings("WeakerAccess")
    4243public class NikonType1MakernoteDirectory extends Directory
    4344{
  • trunk/src/com/drew/metadata/exif/makernotes/NikonType2MakernoteDescriptor.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3737 * @author Drew Noakes https://drewnoakes.com
    3838 */
     39@SuppressWarnings("WeakerAccess")
    3940public class NikonType2MakernoteDescriptor extends TagDescriptor<NikonType2MakernoteDirectory>
    4041{
  • trunk/src/com/drew/metadata/exif/makernotes/NikonType2MakernoteDirectory.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    4545 * @author Drew Noakes https://drewnoakes.com
    4646 */
     47@SuppressWarnings("WeakerAccess")
    4748public class NikonType2MakernoteDirectory extends Directory
    4849{
     
    760761    public static final int TAG_UNKNOWN_50 = 0x00BD;
    761762    public static final int TAG_UNKNOWN_51 = 0x0103;
    762     public static final int TAG_PRINT_IM = 0x0E00;
     763    public static final int TAG_PRINT_IMAGE_MATCHING_INFO = 0x0E00;
    763764
    764765    /**
     
    893894        _tagNameMap.put(TAG_UNKNOWN_50, "Unknown 50");
    894895        _tagNameMap.put(TAG_UNKNOWN_51, "Unknown 51");
    895         _tagNameMap.put(TAG_PRINT_IM, "Print IM");
     896        _tagNameMap.put(TAG_PRINT_IMAGE_MATCHING_INFO, "Print IM");
    896897        _tagNameMap.put(TAG_UNKNOWN_52, "Unknown 52");
    897898        _tagNameMap.put(TAG_UNKNOWN_53, "Unknown 53");
  • trunk/src/com/drew/metadata/exif/makernotes/OlympusCameraSettingsMakernoteDescriptor.java

    r10862 r13061  
    4141 * @author Drew Noakes https://drewnoakes.com
    4242 */
     43@SuppressWarnings("WeakerAccess")
    4344public class OlympusCameraSettingsMakernoteDescriptor extends TagDescriptor<OlympusCameraSettingsMakernoteDirectory>
    4445{
     
    143144            case TagArtFilterEffect:
    144145                return getArtFilterEffectDescription();
     146            case TagColorCreatorEffect:
     147                return getColorCreatorEffectDescription();
    145148
    146149            case TagDriveMode:
     
    407410        int p4 = (int)(values[index + 3].doubleValue() * 100);
    408411
     412        if(p1 + p2 + p3 + p4 == 0)
     413            return "n/a";
     414
    409415        return String.format("(%d%%,%d%%) (%d%%,%d%%)", p1, p2, p3, p4);
    410 
    411416    }
    412417
     
    10191024        StringBuilder sb = new StringBuilder();
    10201025        for (int i = 0; i < values.length; i++) {
    1021             if (i == 1)
    1022                 sb.append("Highlights ");
    1023             else if (i == 5)
    1024                 sb.append("Shadows ");
    1025 
    1026             sb.append(values[i]).append("; ");
     1026            if (i == 0 || i == 4 || i == 8 || i == 12 || i == 16 || i == 20 || i == 24)
     1027                sb.append(_toneLevelType.get(values[i])).append("; ");
     1028            else
     1029                sb.append(values[i]).append("; ");
    10271030        }
    10281031
     
    10341037    {
    10351038        int[] values = _directory.getIntArray(TagArtFilterEffect);
    1036         if (values == null || values.length == 0)
     1039        if (values == null)
    10371040            return null;
    10381041
     
    10401043        for (int i = 0; i < values.length; i++) {
    10411044            if (i == 0) {
    1042                 sb.append((_filters.containsKey(values[i]) ? _filters.get(values[i]) : "[unknown]"));
     1045                sb.append((_filters.containsKey(values[i]) ? _filters.get(values[i]) : "[unknown]")).append("; ");
     1046            } else if (i == 3) {
     1047                sb.append("Partial Color ").append(values[i]).append("; ");
    10431048            } else if (i == 4) {
    10441049                switch (values[i]) {
     
    10681073                        break;
    10691074                }
     1075                sb.append("; ");
     1076            } else if (i == 6) {
     1077                switch (values[i]) {
     1078                    case 0:
     1079                        sb.append("No Color Filter");
     1080                        break;
     1081                    case 1:
     1082                        sb.append("Yellow Color Filter");
     1083                        break;
     1084                    case 2:
     1085                        sb.append("Orange Color Filter");
     1086                        break;
     1087                    case 3:
     1088                        sb.append("Red Color Filter");
     1089                        break;
     1090                    case 4:
     1091                        sb.append("Green Color Filter");
     1092                        break;
     1093                    default:
     1094                        sb.append("Unknown (").append(values[i]).append(")");
     1095                        break;
     1096                }
     1097                sb.append("; ");
    10701098            } else {
    1071                 sb.append(values[i]);
     1099                sb.append(values[i]).append("; ");
    10721100            }
    1073             sb.append("; ");
     1101        }
     1102
     1103        return sb.substring(0, sb.length() - 2);
     1104    }
     1105
     1106    @Nullable
     1107    public String getColorCreatorEffectDescription()
     1108    {
     1109        int[] values = _directory.getIntArray(TagColorCreatorEffect);
     1110        if (values == null)
     1111            return null;
     1112
     1113        StringBuilder sb = new StringBuilder();
     1114        for (int i = 0; i < values.length; i++) {
     1115            if (i == 0) {
     1116                sb.append("Color ").append(values[i]).append("; ");
     1117            } else if (i == 3) {
     1118                sb.append("Strength ").append(values[i]).append("; ");
     1119            } else {
     1120                sb.append(values[i]).append("; ");
     1121            }
    10741122        }
    10751123
     
    12941342    }
    12951343
     1344    @Nullable
    12961345    private String getFiltersDescription(int tagId)
    12971346    {
     
    13121361    }
    13131362
     1363    private static final HashMap<Integer, String> _toneLevelType = new HashMap<Integer, String>();
    13141364    // ArtFilter, ArtFilterEffect and MagicFilter values
    13151365    private static final HashMap<Integer, String> _filters = new HashMap<Integer, String>();
     
    13541404        _filters.put(40, "Partial Color II");
    13551405        _filters.put(41, "Partial Color III");
     1406
     1407        _toneLevelType.put(0, "0");
     1408        _toneLevelType.put(-31999, "Highlights ");
     1409        _toneLevelType.put(-31998, "Shadows ");
     1410        _toneLevelType.put(-31997, "Midtones ");
    13561411    }
    13571412}
  • trunk/src/com/drew/metadata/exif/makernotes/OlympusCameraSettingsMakernoteDirectory.java

    r10862 r13061  
    3333 * @author Drew Noakes https://drewnoakes.com
    3434 */
     35@SuppressWarnings("WeakerAccess")
    3536public class OlympusCameraSettingsMakernoteDirectory extends Directory
    3637{
     
    8990    public static final int TagToneLevel = 0x52e;
    9091    public static final int TagArtFilterEffect = 0x52f;
     92    public static final int TagColorCreatorEffect = 0x532;
    9193
    9294    public static final int TagDriveMode = 0x600;
     
    162164        _tagNameMap.put(TagToneLevel, "Tone Level");
    163165        _tagNameMap.put(TagArtFilterEffect, "Art Filter Effect");
     166        _tagNameMap.put(TagColorCreatorEffect, "Color Creator Effect");
    164167
    165168        _tagNameMap.put(TagDriveMode, "Drive Mode");
  • trunk/src/com/drew/metadata/exif/makernotes/OlympusEquipmentMakernoteDescriptor.java

    r10862 r13061  
    4040 * @author Drew Noakes https://drewnoakes.com
    4141 */
     42@SuppressWarnings("WeakerAccess")
    4243public class OlympusEquipmentMakernoteDescriptor extends TagDescriptor<OlympusEquipmentMakernoteDirectory>
    4344{
     
    5354        switch (tagType) {
    5455            case TAG_EQUIPMENT_VERSION:
    55                 return GetEquipmentVersionDescription();
     56                return getEquipmentVersionDescription();
     57            case TAG_CAMERA_TYPE_2:
     58                return getCameraType2Description();
    5659            case TAG_FOCAL_PLANE_DIAGONAL:
    57                 return GetFocalPlaneDiagonalDescription();
     60                return getFocalPlaneDiagonalDescription();
    5861            case TAG_BODY_FIRMWARE_VERSION:
    59                 return GetBodyFirmwareVersionDescription();
     62                return getBodyFirmwareVersionDescription();
    6063            case TAG_LENS_TYPE:
    61                 return GetLensTypeDescription();
     64                return getLensTypeDescription();
    6265            case TAG_LENS_FIRMWARE_VERSION:
    63                 return GetLensFirmwareVersionDescription();
     66                return getLensFirmwareVersionDescription();
    6467            case TAG_MAX_APERTURE_AT_MIN_FOCAL:
    65                 return GetMaxApertureAtMinFocalDescription();
     68                return getMaxApertureAtMinFocalDescription();
    6669            case TAG_MAX_APERTURE_AT_MAX_FOCAL:
    67                 return GetMaxApertureAtMaxFocalDescription();
     70                return getMaxApertureAtMaxFocalDescription();
    6871            case TAG_MAX_APERTURE:
    69                 return GetMaxApertureDescription();
     72                return getMaxApertureDescription();
    7073            case TAG_LENS_PROPERTIES:
    71                 return GetLensPropertiesDescription();
     74                return getLensPropertiesDescription();
    7275            case TAG_EXTENDER:
    73                 return GetExtenderDescription();
     76                return getExtenderDescription();
    7477            case TAG_FLASH_TYPE:
    75                 return GetFlashTypeDescription();
     78                return getFlashTypeDescription();
    7679            case TAG_FLASH_MODEL:
    77                 return GetFlashModelDescription();
     80                return getFlashModelDescription();
    7881            default:
    7982                return super.getDescription(tagType);
     
    8285
    8386    @Nullable
    84     public String GetEquipmentVersionDescription()
     87    public String getEquipmentVersionDescription()
    8588    {
    8689        return getVersionBytesDescription(TAG_EQUIPMENT_VERSION, 4);
     
    8891
    8992    @Nullable
    90     public String GetFocalPlaneDiagonalDescription()
     93    public String getCameraType2Description()
     94    {
     95        String cameratype = _directory.getString(TAG_CAMERA_TYPE_2);
     96        if(cameratype == null)
     97            return null;
     98
     99        if(OlympusMakernoteDirectory.OlympusCameraTypes.containsKey(cameratype))
     100            return OlympusMakernoteDirectory.OlympusCameraTypes.get(cameratype);
     101
     102        return cameratype;
     103    }
     104
     105    @Nullable
     106    public String getFocalPlaneDiagonalDescription()
    91107    {
    92108        return _directory.getString(TAG_FOCAL_PLANE_DIAGONAL) + " mm";
     
    94110
    95111    @Nullable
    96     public String GetBodyFirmwareVersionDescription()
     112    public String getBodyFirmwareVersionDescription()
    97113    {
    98114        Integer value = _directory.getInteger(TAG_BODY_FIRMWARE_VERSION);
     
    107123
    108124    @Nullable
    109     public String GetLensTypeDescription()
     125    public String getLensTypeDescription()
    110126    {
    111127        String str = _directory.getString(TAG_LENS_TYPE);
     
    140156
    141157    @Nullable
    142     public String GetLensFirmwareVersionDescription()
     158    public String getLensFirmwareVersionDescription()
    143159    {
    144160        Integer value = _directory.getInteger(TAG_LENS_FIRMWARE_VERSION);
     
    153169
    154170    @Nullable
    155     public String GetMaxApertureAtMinFocalDescription()
     171    public String getMaxApertureAtMinFocalDescription()
    156172    {
    157173        Integer value = _directory.getInteger(TAG_MAX_APERTURE_AT_MIN_FOCAL);
     
    164180
    165181    @Nullable
    166     public String GetMaxApertureAtMaxFocalDescription()
     182    public String getMaxApertureAtMaxFocalDescription()
    167183    {
    168184        Integer value = _directory.getInteger(TAG_MAX_APERTURE_AT_MAX_FOCAL);
     
    175191
    176192    @Nullable
    177     public String GetMaxApertureDescription()
     193    public String getMaxApertureDescription()
    178194    {
    179195        Integer value = _directory.getInteger(TAG_MAX_APERTURE);
     
    191207
    192208    @Nullable
    193     public String GetLensPropertiesDescription()
     209    public String getLensPropertiesDescription()
    194210    {
    195211        Integer value = _directory.getInteger(TAG_LENS_PROPERTIES);
     
    201217
    202218    @Nullable
    203     public String GetExtenderDescription()
     219    public String getExtenderDescription()
    204220    {
    205221        String str = _directory.getString(TAG_EXTENDER);
     
    234250
    235251    @Nullable
    236     public String GetFlashTypeDescription()
     252    public String getFlashTypeDescription()
    237253    {
    238254        return getIndexedDescription(TAG_FLASH_TYPE,
     
    241257
    242258    @Nullable
    243     public String GetFlashModelDescription()
     259    public String getFlashModelDescription()
    244260    {
    245261        return getIndexedDescription(TAG_FLASH_MODEL,
  • trunk/src/com/drew/metadata/exif/makernotes/OlympusEquipmentMakernoteDirectory.java

    r10862 r13061  
    3333 * @author Drew Noakes https://drewnoakes.com
    3434 */
     35@SuppressWarnings("WeakerAccess")
    3536public class OlympusEquipmentMakernoteDirectory extends Directory
    3637{
  • trunk/src/com/drew/metadata/exif/makernotes/OlympusMakernoteDescriptor.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2121package com.drew.metadata.exif.makernotes;
    2222
     23import com.drew.imaging.PhotographicConversions;
     24import com.drew.lang.Rational;
    2325import com.drew.lang.DateUtil;
    2426import com.drew.lang.annotations.NotNull;
     
    3638 * @author Drew Noakes https://drewnoakes.com
    3739 */
     40@SuppressWarnings("WeakerAccess")
    3841public class OlympusMakernoteDescriptor extends TagDescriptor<OlympusMakernoteDirectory>
    3942{
     
    6669            case TAG_BW_MODE:
    6770                return getBWModeDescription();
    68             case TAG_DIGI_ZOOM_RATIO:
    69                 return getDigiZoomRatioDescription();
     71            case TAG_DIGITAL_ZOOM:
     72                return getDigitalZoomDescription();
     73            case TAG_FOCAL_PLANE_DIAGONAL:
     74                return getFocalPlaneDiagonalDescription();
     75            case TAG_CAMERA_TYPE:
     76                return getCameraTypeDescription();
    7077            case TAG_CAMERA_ID:
    7178                return getCameraIdDescription();
     79            case TAG_ONE_TOUCH_WB:
     80                return getOneTouchWbDescription();
     81            case TAG_SHUTTER_SPEED_VALUE:
     82                return getShutterSpeedDescription();
     83            case TAG_ISO_VALUE:
     84                return getIsoValueDescription();
     85            case TAG_APERTURE_VALUE:
     86                return getApertureValueDescription();
    7287            case TAG_FLASH_MODE:
    7388                return getFlashModeDescription();
     
    7893            case TAG_SHARPNESS:
    7994                return getSharpnessDescription();
     95            case TAG_COLOUR_MATRIX:
     96                return getColorMatrixDescription();
     97            case TAG_WB_MODE:
     98                return getWbModeDescription();
     99            case TAG_RED_BALANCE:
     100                return getRedBalanceDescription();
     101            case TAG_BLUE_BALANCE:
     102                return getBlueBalanceDescription();
     103            case TAG_CONTRAST:
     104                return getContrastDescription();
     105            case TAG_PREVIEW_IMAGE_VALID:
     106                return getPreviewImageValidDescription();
    80107
    81108            case CameraSettings.TAG_EXPOSURE_MODE:
     
    102129                return getMacroModeCameraSettingDescription();
    103130            case CameraSettings.TAG_DIGITAL_ZOOM:
    104                 return getDigitalZoomDescription();
     131                return getDigitalZoomCameraSettingDescription();
    105132            case CameraSettings.TAG_EXPOSURE_COMPENSATION:
    106133                return getExposureCompensationDescription();
     
    138165                return getSaturationDescription();
    139166            case CameraSettings.TAG_CONTRAST:
    140                 return getContrastDescription();
     167                return getContrastCameraSettingDescription();
    141168            case CameraSettings.TAG_SHARPNESS:
    142169                return getSharpnessCameraSettingDescription();
     
    305332
    306333    @Nullable
    307     public String getDigitalZoomDescription()
     334    public String getDigitalZoomCameraSettingDescription()
    308335    {
    309336        return getIndexedDescription(CameraSettings.TAG_DIGITAL_ZOOM, "Off", "Electronic magnification", "Digital zoom 2x");
     
    468495
    469496    @Nullable
    470     public String getContrastDescription()
     497    public String getContrastCameraSettingDescription()
    471498    {
    472499        Long value = _directory.getLongObject(CameraSettings.TAG_CONTRAST);
     
    647674
    648675    @Nullable
     676    public String getColorMatrixDescription()
     677    {
     678        int[] obj = _directory.getIntArray(TAG_COLOUR_MATRIX);
     679        if (obj == null)
     680            return null;
     681
     682        StringBuilder sb = new StringBuilder();
     683        for (int i = 0; i < obj.length; i++) {
     684            sb.append((short)obj[i]);
     685            if (i < obj.length - 1)
     686                sb.append(" ");
     687        }
     688        return sb.length() == 0 ? null : sb.toString();
     689    }
     690
     691    @Nullable
     692    public String getWbModeDescription()
     693    {
     694        int[] obj = _directory.getIntArray(TAG_WB_MODE);
     695        if (obj == null)
     696            return null;
     697
     698        String val = String.format("%d %d", obj[0], obj[1]);
     699
     700        if(val.equals("1 0"))
     701            return "Auto";
     702        else if(val.equals("1 2"))
     703            return "Auto (2)";
     704        else if(val.equals("1 4"))
     705            return "Auto (4)";
     706        else if(val.equals("2 2"))
     707            return "3000 Kelvin";
     708        else if(val.equals("2 3"))
     709            return "3700 Kelvin";
     710        else if(val.equals("2 4"))
     711            return "4000 Kelvin";
     712        else if(val.equals("2 5"))
     713            return "4500 Kelvin";
     714        else if(val.equals("2 6"))
     715            return "5500 Kelvin";
     716        else if(val.equals("2 7"))
     717            return "6500 Kelvin";
     718        else if(val.equals("2 8"))
     719            return "7500 Kelvin";
     720        else if(val.equals("3 0"))
     721            return "One-touch";
     722        else
     723            return "Unknown " + val;
     724    }
     725
     726    @Nullable
     727    public String getRedBalanceDescription()
     728    {
     729        int[] values = _directory.getIntArray(TAG_RED_BALANCE);
     730        if (values == null)
     731            return null;
     732
     733        short value = (short)values[0];
     734
     735        return String.valueOf((double)value/256d);
     736    }
     737
     738    @Nullable
     739    public String getBlueBalanceDescription()
     740    {
     741        int[] values = _directory.getIntArray(TAG_BLUE_BALANCE);
     742        if (values == null)
     743            return null;
     744
     745        short value = (short)values[0];
     746
     747        return String.valueOf((double)value/256d);
     748    }
     749
     750    @Nullable
     751    public String getContrastDescription()
     752    {
     753        return getIndexedDescription(TAG_CONTRAST, "High", "Normal", "Low");
     754    }
     755
     756    @Nullable
     757    public String getPreviewImageValidDescription()
     758    {
     759        return getIndexedDescription(TAG_PREVIEW_IMAGE_VALID, "No", "Yes");
     760    }
     761
     762    @Nullable
    649763    public String getFocusModeDescription()
    650764    {
     
    665779
    666780    @Nullable
    667     public String getDigiZoomRatioDescription()
    668     {
    669         return getIndexedDescription(TAG_DIGI_ZOOM_RATIO, "Normal", null, "Digital 2x Zoom");
     781    public String getDigitalZoomDescription()
     782    {
     783        Rational value = _directory.getRational(TAG_DIGITAL_ZOOM);
     784        if (value == null)
     785            return null;
     786        return value.toSimpleString(false);
     787    }
     788
     789    @Nullable
     790    public String getFocalPlaneDiagonalDescription()
     791    {
     792        Rational value = _directory.getRational(TAG_FOCAL_PLANE_DIAGONAL);
     793        if (value == null)
     794            return null;
     795
     796        DecimalFormat format = new DecimalFormat("0.###");
     797        return format.format(value.doubleValue()) + " mm";
     798    }
     799
     800    @Nullable
     801    public String getCameraTypeDescription()
     802    {
     803        String cameratype = _directory.getString(TAG_CAMERA_TYPE);
     804        if(cameratype == null)
     805            return null;
     806
     807        if(OlympusMakernoteDirectory.OlympusCameraTypes.containsKey(cameratype))
     808            return OlympusMakernoteDirectory.OlympusCameraTypes.get(cameratype);
     809
     810        return cameratype;
    670811    }
    671812
     
    680821
    681822    @Nullable
     823    public String getOneTouchWbDescription()
     824    {
     825        return getIndexedDescription(TAG_ONE_TOUCH_WB, "Off", "On", "On (Preset)");
     826    }
     827
     828    @Nullable
     829    public String getShutterSpeedDescription()
     830    {
     831        return super.getShutterSpeedDescription(TAG_SHUTTER_SPEED_VALUE);
     832    }
     833
     834    @Nullable
     835    public String getIsoValueDescription()
     836    {
     837        Rational value = _directory.getRational(TAG_ISO_VALUE);
     838        if (value == null)
     839            return null;
     840
     841        return String.valueOf(Math.round(Math.pow(2, value.doubleValue() - 5) * 100));
     842    }
     843
     844    @Nullable
     845    public String getApertureValueDescription()
     846    {
     847        Double aperture = _directory.getDoubleObject(TAG_APERTURE_VALUE);
     848        if (aperture == null)
     849            return null;
     850        double fStop = PhotographicConversions.apertureToFStop(aperture);
     851        return getFStopDescription(fStop);
     852    }
     853
     854    @Nullable
    682855    public String getMacroModeDescription()
    683856    {
     
    694867    public String getJpegQualityDescription()
    695868    {
    696         return getIndexedDescription(TAG_JPEG_QUALITY,
     869        String cameratype = _directory.getString(TAG_CAMERA_TYPE);
     870
     871        if(cameratype != null)
     872        {
     873            Integer value = _directory.getInteger(TAG_JPEG_QUALITY);
     874            if(value == null)
     875                return null;
     876
     877            if((cameratype.startsWith("SX") && !cameratype.startsWith("SX151"))
     878                || cameratype.startsWith("D4322"))
     879            {
     880                switch (value)
     881                {
     882                    case 0:
     883                        return "Standard Quality (Low)";
     884                    case 1:
     885                        return "High Quality (Normal)";
     886                    case 2:
     887                        return "Super High Quality (Fine)";
     888                    case 6:
     889                        return "RAW";
     890                    default:
     891                        return "Unknown (" + value.toString() + ")";
     892                }
     893            }
     894            else
     895            {
     896                switch (value)
     897                {
     898                    case 0:
     899                        return "Standard Quality (Low)";
     900                    case 1:
     901                        return "High Quality (Normal)";
     902                    case 2:
     903                        return "Super High Quality (Fine)";
     904                    case 4:
     905                        return "RAW";
     906                    case 5:
     907                        return "Medium-Fine";
     908                    case 6:
     909                        return "Small-Fine";
     910                    case 33:
     911                        return "Uncompressed";
     912                    default:
     913                        return "Unknown (" + value.toString() + ")";
     914                }
     915            }
     916        }
     917        else
     918            return getIndexedDescription(TAG_JPEG_QUALITY,
    697919            1,
    698920            "Standard Quality",
  • trunk/src/com/drew/metadata/exif/makernotes/OlympusMakernoteDirectory.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3434 * @author Drew Noakes https://drewnoakes.com
    3535&n