OSDN Git Service

Re-design ExifParser for some strange Exif header
authorEarl Ou <shunhsingou@google.com>
Tue, 14 Aug 2012 09:50:08 +0000 (17:50 +0800)
committerEarl Ou <shunhsingou@google.com>
Fri, 24 Aug 2012 08:35:19 +0000 (16:35 +0800)
The previous design failed to parser file with strange IFD position.
Therefore we need a new design.

Change-Id: I9f008a9259df6ef7d9a5022e126d6f25069e3d00

src/com/android/gallery3d/exif/ExifData.java
src/com/android/gallery3d/exif/ExifParser.java
src/com/android/gallery3d/exif/ExifReader.java
src/com/android/gallery3d/exif/ExifTag.java
src/com/android/gallery3d/exif/IfdData.java
src/com/android/gallery3d/exif/IfdId.java [new file with mode: 0644]
src/com/android/gallery3d/exif/IfdParser.java [deleted file]
src/com/android/gallery3d/exif/Rational.java
tests/src/com/android/gallery3d/exif/ExifParserTest.java
tests/src/com/android/gallery3d/exif/ExifReaderTest.java
tests/src/com/android/gallery3d/exif/ExifXmlReader.java

index 2c316f7..712698e 100644 (file)
  */
 
 package com.android.gallery3d.exif;
-
-
+/**
+ *  This class stores the EXIF header in IFDs according to the JPEG specification.
+ *  It is the result produced by {@link ExifReader}.
+ *  @see ExifReader
+ *  @see IfdData
+ */
 public class ExifData {
-    public static final int TYPE_IFD_0 = 0;
-    public static final int TYPE_IFD_EXIF = 1;
-    public static final int TYPE_IFD_1 = 2;
-    public static final int TYPE_IFD_GPS = 3;
-    public static final int TYPE_IFD_INTEROPERABILITY = 4;
-
-    private final IfdData[] mIfdDatas = new IfdData[5];
+    private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT];
 
-    public IfdData getIfdData(int ifdType) {
-        return mIfdDatas[ifdType];
+    /**
+     * Gets the IFD data of the specified IFD.
+     *
+     * @see IfdId#TYPE_IFD_0
+     * @see IfdId#TYPE_IFD_1
+     * @see IfdId#TYPE_IFD_EXIF
+     * @see IfdId#TYPE_IFD_GPS
+     * @see IfdId#TYPE_IFD_INTEROPERABILITY
+     */
+    public IfdData getIfdData(int ifdId) {
+        return mIfdDatas[ifdId];
     }
 
+    /**
+     * Adds IFD data. If IFD data of the same type already exists,
+     * it will be replaced by the new data.
+     */
     public void addIfdData(IfdData data) {
-        mIfdDatas[data.getIfdType()] = data;
+        mIfdDatas[data.getId()] = data;
     }
-}
+}
\ No newline at end of file
index f536a55..4249a72 100644 (file)
@@ -21,10 +21,106 @@ import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.util.Map.Entry;
+import java.util.TreeMap;
 
+/**
+ * This class provides a low-level EXIF parsing API. Given a JPEG format InputStream, the caller
+ * can request which IFD's to read via {@link #parse(InputStream, int)} with given options.
+ * <p>
+ * Below is an example of getting EXIF data from IFD 0 and EXIF IFD using the parser.
+ * <pre>
+ * void parse() {
+ *     ExifParser parser = ExifParser.parse(mImageInputStream,
+ *             ExifParser.OPTION_IFD_0 | ExifParser.OPTIONS_IFD_EXIF);
+ *     int event = parser.next();
+ *     while (event != ExifParser.EVENT_END) {
+ *         switch (event) {
+ *             case ExifParser.EVENT_START_OF_IFD:
+ *                 break;
+ *             case ExifParser.EVENT_NEW_TAG:
+ *                 ExifTag tag = parser.getTag();
+ *                 if (!tag.hasValue()) {
+ *                     parser.registerForTagValue(tag);
+ *                 } else {
+ *                     processTag(tag);
+ *                 }
+ *                 break;
+ *             case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+ *                 tag = parser.getTag();
+ *                 if (tag.getDataType() != ExifTag.TYPE_UNDEFINED) {
+ *                     processTag(tag);
+ *                 }
+ *                 break;
+ *         }
+ *         event = parser.next();
+ *     }
+ * }
+ *
+ * void processTag(ExifTag tag) {
+ *     // process the tag as you like.
+ * }
+ * </pre>
+ */
 public class ExifParser {
+    /**
+     * When the parser reaches a new IFD area. Call
+     *  {@link #getCurrentIfd()} to know which IFD we are in.
+     */
+    public static final int EVENT_START_OF_IFD = 0;
+    /**
+     * When the parser reaches a new tag. Call {@link #getTag()}to get the
+     * corresponding tag.
+     */
+    public static final int EVENT_NEW_TAG = 1;
+    /**
+     * When the parser reaches the value area of tag that is registered by
+     *  {@link #registerForTagValue(ExifTag)} previously. Call
+     *  {@link #getTag()} to get the corresponding tag.
+     */
+    public static final int EVENT_VALUE_OF_REGISTERED_TAG = 2;
 
-    private static final String TAG = "ExifParser";
+    /**
+     * When the parser reaches the compressed image area.
+     */
+    public static final int EVENT_COMPRESSED_IMAGE = 3;
+    /**
+     * When the parser reaches the uncompressed image strip.
+     *  Call {@link #getStripIndex()} to get the index of the strip.
+     * @see #getStripIndex()
+     * @see #getStripCount()
+     */
+    public static final int EVENT_UNCOMPRESSED_STRIP = 4;
+    /**
+     * When there is nothing more to parse.
+     */
+    public static final int EVENT_END = 5;
+
+    /**
+     * Option bit to request to parse IFD0.
+     */
+    public static final int OPTION_IFD_0 = 1 << 0;
+    /**
+     * Option bit to request to parse IFD1.
+     */
+    public static final int OPTION_IFD_1 = 1 << 1;
+    /**
+     * Option bit to request to parse Exif-IFD.
+     */
+    public static final int OPTION_IFD_EXIF = 1 << 2;
+    /**
+     * Option bit to request to parse GPS-IFD.
+     */
+    public static final int OPTION_IFD_GPS = 1 << 3;
+    /**
+     * Option bit to request to parse Interoperability-IFD.
+     */
+    public static final int OPTION_IFD_INTEROPERABILITY = 1 << 4;
+    /**
+     * Option bit to request to parse thumbnail.
+     */
+    public static final int OPTION_THUMBNAIL = 1 << 5;
 
     private static final short SOI =  (short) 0xFFD8; // SOI marker of JPEG
     private static final short APP1 = (short) 0xFFE1; // APP1 marker of JPEG
@@ -38,41 +134,471 @@ public class ExifParser {
     private static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM"
     private static final short TIFF_HEADER_TAIL = 0x002A;
 
-    public IfdParser parse(InputStream inputStream) throws ExifInvalidFormatException, IOException{
-        if (!seekTiffData(inputStream)) {
-            return null;
+    private static final int TAG_SIZE = 12;
+    private static final int OFFSET_SIZE = 2;
+
+    private final TiffInputStream mTiffStream;
+    private final int mOptions;
+    private int mIfdStartOffset = 0;
+    private int mNumOfTagInIfd = 0;
+    private int mIfdType;
+    private ExifTag mTag;
+    private ImageEvent mImageEvent;
+    private int mStripCount;
+    private ExifTag mStripSizeTag;
+    private ExifTag mJpegSizeTag;
+    private boolean mNeedToParseOffsetsInCurrentIfd;
+
+    private final TreeMap<Integer, Object> mCorrespondingEvent = new TreeMap<Integer, Object>();
+
+    private boolean isIfdRequested(int ifdType) {
+        switch (ifdType) {
+            case IfdId.TYPE_IFD_0:
+                return (mOptions & OPTION_IFD_0) != 0;
+            case IfdId.TYPE_IFD_1:
+                return (mOptions & OPTION_IFD_1) != 0;
+            case IfdId.TYPE_IFD_EXIF:
+                return (mOptions & OPTION_IFD_EXIF) != 0;
+            case IfdId.TYPE_IFD_GPS:
+                return (mOptions & OPTION_IFD_GPS) != 0;
+            case IfdId.TYPE_IFD_INTEROPERABILITY:
+                return (mOptions & OPTION_IFD_INTEROPERABILITY) != 0;
+        }
+        return false;
+    }
+
+    private boolean isThumbnailRequested() {
+        return (mOptions & OPTION_THUMBNAIL) != 0;
+    }
+
+    private ExifParser(InputStream inputStream, int options)
+            throws IOException, ExifInvalidFormatException {
+        seekTiffData(inputStream);
+        mTiffStream = new TiffInputStream(inputStream);
+        mOptions = options;
+        if (mTiffStream.getReadByteCount() == 0) {
+            parseTiffHeader();
+            long offset = mTiffStream.readUnsignedInt();
+            registerIfd(IfdId.TYPE_IFD_0, offset);
+        }
+    }
+
+    /**
+     * Parses the the given InputStream with the given options
+     * @exception IOException
+     * @exception ExifInvalidFormatException
+     */
+    public static ExifParser parse(InputStream inputStream, int options)
+             throws IOException, ExifInvalidFormatException {
+         return new ExifParser(inputStream, options);
+    }
+
+    /**
+     * Parses the the given InputStream with default options; that is, every IFD and thumbnaill
+     * will be parsed.
+     * @exception IOException
+     * @exception ExifInvalidFormatException
+     * @see #parse(InputStream, int)
+     */
+    public static ExifParser parse(InputStream inputStream)
+            throws IOException, ExifInvalidFormatException {
+        return new ExifParser(inputStream, OPTION_IFD_0 | OPTION_IFD_1
+                | OPTION_IFD_EXIF | OPTION_IFD_GPS | OPTION_IFD_INTEROPERABILITY
+                | OPTION_THUMBNAIL);
+    }
+
+    /**
+     * Moves the parser forward and returns the next parsing event
+     *
+     * @exception IOException
+     * @exception ExifInvalidFormatException
+     * @see #EVENT_START_OF_IFD
+     * @see #EVENT_NEW_TAG
+     * @see #EVENT_VALUE_OF_REGISTERED_TAG
+     * @see #EVENT_COMPRESSED_IMAGE
+     * @see #EVENT_UNCOMPRESSED_STRIP
+     * @see #EVENT_END
+     */
+    public int next() throws IOException, ExifInvalidFormatException {
+        int offset = mTiffStream.getReadByteCount();
+        int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
+        if (offset < endOfTags) {
+            mTag = readTag();
+            if (mNeedToParseOffsetsInCurrentIfd) {
+                checkOffsetOrImageTag(mTag);
+            }
+            return EVENT_NEW_TAG;
+        } else if (offset == endOfTags) {
+            long ifdOffset = readUnsignedInt();
+            // There is a link to ifd1 at the end of ifd0
+            if (mIfdType == IfdId.TYPE_IFD_0) {
+                if (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested()) {
+                    if (ifdOffset != 0) {
+                        registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
+                    }
+                }
+            } else {
+                if (ifdOffset != 0) {
+                    throw new ExifInvalidFormatException("Invalid link to next IFD");
+                }
+            }
+        }
+        while(mCorrespondingEvent.size() != 0) {
+            Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
+            Object event = entry.getValue();
+            skipTo(entry.getKey());
+            if (event instanceof IfdEvent) {
+                mIfdType = ((IfdEvent) event).ifd;
+                mNumOfTagInIfd = mTiffStream.readUnsignedShort();
+                mIfdStartOffset = entry.getKey();
+                mNeedToParseOffsetsInCurrentIfd = needToParseOffsetsInCurrentIfd();
+                if (((IfdEvent) event).isRequested) {
+                    return EVENT_START_OF_IFD;
+                } else {
+                    skipRemainingTagsInCurrentIfd();
+                }
+            } else if (event instanceof ImageEvent) {
+                mImageEvent = (ImageEvent) event;
+                return mImageEvent.type;
+            } else {
+                ExifTagEvent tagEvent = (ExifTagEvent) event;
+                mTag = tagEvent.tag;
+                if (mTag.getDataType() != ExifTag.TYPE_UNDEFINED) {
+                    readFullTagValue(mTag);
+                    checkOffsetOrImageTag(mTag);
+                }
+                if (tagEvent.isRequested) {
+                    return EVENT_VALUE_OF_REGISTERED_TAG;
+                }
+            }
+        }
+        return EVENT_END;
+    }
+
+    /**
+     * Skips the tags area of current IFD, if the parser is not in the tag area, nothing will
+     * happen.
+     *
+     * @throws IOException
+     * @throws ExifInvalidFormatException
+     */
+    public void skipRemainingTagsInCurrentIfd() throws IOException, ExifInvalidFormatException {
+        int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
+        int offset = mTiffStream.getReadByteCount();
+        if (offset > endOfTags) return;
+        if (mNeedToParseOffsetsInCurrentIfd) {
+            while (offset < endOfTags) {
+                mTag = readTag();
+                checkOffsetOrImageTag(mTag);
+                offset += TAG_SIZE;
+            }
+        } else {
+            skipTo(endOfTags);
+        }
+        long ifdOffset = readUnsignedInt();
+        // For ifd0, there is a link to ifd1 in the end of all tags
+        if (mIfdType == IfdId.TYPE_IFD_0
+                && (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested())) {
+            if (ifdOffset > 0) {
+                registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
+            }
+        }
+    }
+
+    private boolean needToParseOffsetsInCurrentIfd() {
+        switch (mIfdType) {
+            case IfdId.TYPE_IFD_0:
+                return isIfdRequested(IfdId.TYPE_IFD_EXIF) || isIfdRequested(IfdId.TYPE_IFD_GPS)
+                        || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY);
+            case IfdId.TYPE_IFD_1:
+                return isThumbnailRequested();
+            case IfdId.TYPE_IFD_EXIF:
+                // The offset to interoperability IFD is located in Exif IFD
+                return isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY);
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * If {@link #next()} return {@link #EVENT_NEW_TAG} or {@link #EVENT_VALUE_OF_REGISTERED_TAG},
+     * call this function to get the corresponding tag.
+     * <p>
+     *
+     * For {@link #EVENT_NEW_TAG}, the tag may not contain the value if the size of the value is
+     * greater than 4 bytes. One should call {@link ExifTag#hasValue()} to check if the tag
+     * contains value.
+     * If there is no value,call {@link #registerForTagValue(ExifTag)} to have the parser emit
+     * {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area pointed by the offset.
+     *
+     * <p>
+     * When {@link #EVENT_VALUE_OF_REGISTERED_TAG} is emitted, the value of the tag will have
+     * already been read except for tags of undefined type. For tags of undefined type, call
+     * one of the read methods to get the value.
+     *
+     * @see #registerForTagValue(ExifTag)
+     * @see #read(byte[])
+     * @see #read(byte[], int, int)
+     * @see #readInt()
+     * @see #readRational()
+     * @see #readShort()
+     * @see #readString(int)
+     * @see #readString(int, Charset)
+     */
+    public ExifTag getTag() {
+        return mTag;
+    }
+
+    /**
+     * Gets number of tags in the current IFD area.
+     */
+    public int getTagCountInCurrentIfd() {
+        return mNumOfTagInIfd;
+    }
+
+    /**
+     * Gets the ID of current IFD.
+     *
+     * @see IfdId#TYPE_IFD_0
+     * @see IfdId#TYPE_IFD_1
+     * @see IfdId#TYPE_IFD_GPS
+     * @see IfdId#TYPE_IFD_INTEROPERABILITY
+     * @see IfdId#TYPE_IFD_EXIF
+     */
+    public int getCurrentIfd() {
+        return mIfdType;
+    }
+
+    /**
+     * When receiving {@link #EVENT_UNCOMPRESSED_STRIP},
+     * call this function to get the index of this strip.
+     * @see #getStripCount()
+     */
+    public int getStripIndex() {
+        return mImageEvent.stripIndex;
+    }
+
+    /**
+     * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to get the number
+     * of strip data.
+     * @see #getStripIndex()
+     */
+    public int getStripCount() {
+        return mStripCount;
+    }
+
+    /**
+     * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to get the strip size.
+     */
+    public int getStripSize() {
+        if (mStripSizeTag == null) return 0;
+        if (mStripSizeTag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
+            return mStripSizeTag.getUnsignedShort(mImageEvent.stripIndex);
+        } else {
+            return (int) mStripSizeTag.getUnsignedInt(mImageEvent.stripIndex);
         }
-        TiffInputStream tiffStream = new TiffInputStream(inputStream);
-        parseTiffHeader(tiffStream);
-        long offset = tiffStream.readUnsignedInt();
-        if (offset > Integer.MAX_VALUE) {
-            throw new ExifInvalidFormatException("Offset value is larger than Integer.MAX_VALUE");
+    }
+
+    /**
+     * When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get the image data
+     * size.
+     */
+    public int getCompressedImageSize() {
+        if (mJpegSizeTag == null) return 0;
+        return (int) mJpegSizeTag.getUnsignedInt();
+    }
+
+    private void skipTo(int offset) throws IOException {
+        mTiffStream.skipTo(offset);
+        while (!mCorrespondingEvent.isEmpty() && mCorrespondingEvent.firstKey() < offset) {
+            mCorrespondingEvent.pollFirstEntry();
         }
-        return new IfdParser(tiffStream, (int)offset);
     }
 
-    private void parseTiffHeader(TiffInputStream tiffStream) throws IOException,
+    /**
+     * When getting {@link #EVENT_NEW_TAG} in the tag area of IFD,
+     * the tag may not contain the value if the size of the value is greater than 4 bytes.
+     * When the value is not available here, call this method so that the parser will emit
+     * {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area where the value is located.
+
+     * @see #EVENT_VALUE_OF_REGISTERED_TAG
+     */
+    public void registerForTagValue(ExifTag tag) {
+        mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, true));
+    }
+
+    private void registerIfd(int ifdType, long offset) {
+        mCorrespondingEvent.put((int) offset, new IfdEvent(ifdType, isIfdRequested(ifdType)));
+    }
+
+    private void registerCompressedImage(long offset) {
+        mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_COMPRESSED_IMAGE));
+    }
+
+    private void registerUncompressedStrip(int stripIndex, long offset) {
+        mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_UNCOMPRESSED_STRIP
+                , stripIndex));
+    }
+
+    private ExifTag readTag() throws IOException, ExifInvalidFormatException {
+        short tagId = mTiffStream.readShort();
+        short dataFormat = mTiffStream.readShort();
+        long numOfComp = mTiffStream.readUnsignedInt();
+        if (numOfComp > Integer.MAX_VALUE) {
+            throw new ExifInvalidFormatException(
+                    "Number of component is larger then Integer.MAX_VALUE");
+        }
+        ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType);
+        if (tag.getDataSize() > 4) {
+            long offset = mTiffStream.readUnsignedInt();
+            if (offset > Integer.MAX_VALUE) {
+                throw new ExifInvalidFormatException(
+                        "offset is larger then Integer.MAX_VALUE");
+            }
+            tag.setOffset((int) offset);
+        } else {
+            readFullTagValue(tag);
+            mTiffStream.skip(4 - tag.getDataSize());
+        }
+        return tag;
+    }
+
+    /**
+     * Check the tag, if the tag is one of the offset tag that points to the IFD or image the
+     * caller is interested in, register the IFD or image.
+     */
+    private void checkOffsetOrImageTag(ExifTag tag) {
+        switch (tag.getTagId()) {
+            case ExifTag.TIFF_TAG.TAG_EXIF_IFD:
+                if (isIfdRequested(IfdId.TYPE_IFD_EXIF)
+                        || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
+                    registerIfd(IfdId.TYPE_IFD_EXIF, tag.getUnsignedInt());
+                }
+                break;
+            case ExifTag.TIFF_TAG.TAG_GPS_IFD:
+                if (isIfdRequested(IfdId.TYPE_IFD_GPS)) {
+                    registerIfd(IfdId.TYPE_IFD_GPS, tag.getUnsignedInt());
+                }
+                break;
+            case ExifTag.EXIF_TAG.TAG_INTEROPERABILITY_IFD:
+                if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
+                    registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getUnsignedInt());
+                }
+                break;
+            case ExifTag.TIFF_TAG.TAG_JPEG_INTERCHANGE_FORMAT:
+                if (isThumbnailRequested()) {
+                    registerCompressedImage(tag.getUnsignedInt());
+                }
+                break;
+            case ExifTag.TIFF_TAG.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
+                if (isThumbnailRequested()) {
+                    mJpegSizeTag = tag;
+                }
+                break;
+            case ExifTag.TIFF_TAG.TAG_STRIP_OFFSETS:
+                if (isThumbnailRequested()) {
+                    if (tag.hasValue()) {
+                        for (int i = 0; i < tag.getComponentCount(); i++) {
+                            if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
+                                registerUncompressedStrip(i, tag.getUnsignedShort(i));
+                            } else {
+                                registerUncompressedStrip(i, tag.getUnsignedInt(i));
+                            }
+                        }
+                    } else {
+                        mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, false));
+                    }
+                }
+                break;
+            case ExifTag.TIFF_TAG.TAG_STRIP_BYTE_COUNTS:
+                if (isThumbnailRequested()) {
+                    if (tag.hasValue()) {
+                        mStripSizeTag = tag;
+                    }
+                }
+                break;
+        }
+    }
+
+    private void readFullTagValue(ExifTag tag) throws IOException {
+        switch(tag.getDataType()) {
+            case ExifTag.TYPE_UNSIGNED_BYTE:
+            case ExifTag.TYPE_UNDEFINED:
+                {
+                    byte buf[] = new byte[tag.getComponentCount()];
+                    read(buf);
+                    tag.setValue(buf);
+                }
+                break;
+            case ExifTag.TYPE_ASCII:
+                tag.setValue(readString(tag.getComponentCount()));
+                break;
+            case ExifTag.TYPE_UNSIGNED_INT:
+                {
+                    long value[] = new long[tag.getComponentCount()];
+                    for (int i = 0, n = value.length; i < n; i++) {
+                        value[i] = readUnsignedInt();
+                    }
+                    tag.setValue(value);
+                }
+                break;
+          case ExifTag.TYPE_UNSIGNED_RATIONAL:
+              {
+                  Rational value[] = new Rational[tag.getComponentCount()];
+                  for (int i = 0, n = value.length; i < n; i++) {
+                      value[i] = readUnsignedRational();
+                  }
+                  tag.setValue(value);
+              }
+              break;
+          case ExifTag.TYPE_UNSIGNED_SHORT:
+              {
+                  int value[] = new int[tag.getComponentCount()];
+                  for (int i = 0, n = value.length; i < n; i++) {
+                      value[i] = readUnsignedShort();
+                  }
+                  tag.setValue(value);
+              }
+              break;
+          case ExifTag.TYPE_INT:
+              {
+                  int value[] = new int[tag.getComponentCount()];
+                  for (int i = 0, n = value.length; i < n; i++) {
+                      value[i] = readInt();
+                  }
+                  tag.setValue(value);
+              }
+              break;
+          case ExifTag.TYPE_RATIONAL:
+              {
+                  Rational value[] = new Rational[tag.getComponentCount()];
+                  for (int i = 0, n = value.length; i < n; i++) {
+                      value[i] = readRational();
+                  }
+                  tag.setValue(value);
+              }
+              break;
+        }
+    }
+
+    private void parseTiffHeader() throws IOException,
             ExifInvalidFormatException {
-        short byteOrder = tiffStream.readShort();
+        short byteOrder = mTiffStream.readShort();
         ByteOrder order;
         if (LITTLE_ENDIAN_TAG == byteOrder) {
-            tiffStream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+            mTiffStream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
         } else if (BIG_ENDIAN_TAG == byteOrder) {
-            tiffStream.setByteOrder(ByteOrder.BIG_ENDIAN);
+            mTiffStream.setByteOrder(ByteOrder.BIG_ENDIAN);
         } else {
             throw new ExifInvalidFormatException("Invalid TIFF header");
         }
 
-        if (tiffStream.readShort() != TIFF_HEADER_TAIL) {
+        if (mTiffStream.readShort() != TIFF_HEADER_TAIL) {
             throw new ExifInvalidFormatException("Invalid TIFF header");
         }
     }
 
-    /**
-     * Try to seek the tiff data. If there is no tiff data, return false, else return true and
-     * the inputstream will be at the start of tiff data
-     */
-    private boolean seekTiffData(InputStream inputStream) throws IOException,
+    private void seekTiffData(InputStream inputStream) throws IOException,
             ExifInvalidFormatException {
         DataInputStream dataStream = new DataInputStream(inputStream);
 
@@ -91,7 +617,7 @@ public class ExifParser {
         }
 
         if (tag != APP1) {
-            return false;
+            throw new ExifInvalidFormatException("No APP1 segment");
         }
 
         // APP1 length, it's not used for us
@@ -101,9 +627,90 @@ public class ExifParser {
         if (dataStream.readInt() != EXIF_HEADER
                 || dataStream.readShort() != EXIF_HEADER_TAIL) {
             // There is no EXIF data;
-            return false;
+            throw new ExifInvalidFormatException("No Exif header in APP1");
+        }
+    }
+
+    public int read(byte[] buffer, int offset, int length) throws IOException {
+        return mTiffStream.read(buffer, offset, length);
+    }
+
+    public int read(byte[] buffer) throws IOException {
+        return mTiffStream.read(buffer);
+    }
+
+    public String readString(int n) throws IOException {
+        if (n > 0) {
+            byte[] buf = new byte[n];
+            mTiffStream.readOrThrow(buf);
+            return new String(buf, 0, n - 1, "UTF8");
+        } else {
+            return "";
+        }
+    }
+
+    public String readString(int n, Charset charset) throws IOException {
+        byte[] buf = new byte[n];
+        mTiffStream.readOrThrow(buf);
+        return new String(buf, 0, n - 1, charset);
+    }
+
+    public int readUnsignedShort() throws IOException {
+        return readShort() & 0xffff;
+    }
+
+    public long readUnsignedInt() throws IOException {
+        return readInt() & 0xffffffffL;
+    }
+
+    public Rational readUnsignedRational() throws IOException {
+        long nomi = readUnsignedInt();
+        long denomi = readUnsignedInt();
+        return new Rational(nomi, denomi);
+    }
+
+    public int readInt() throws IOException {
+        return mTiffStream.readInt();
+    }
+
+    public short readShort() throws IOException {
+        return mTiffStream.readShort();
+    }
+
+    public Rational readRational() throws IOException {
+        int nomi = readInt();
+        int denomi = readInt();
+        return new Rational(nomi, denomi);
+    }
+
+    private static class ImageEvent {
+        int stripIndex;
+        int type;
+        ImageEvent(int type) {
+            this.stripIndex = 0;
+            this.type = type;
         }
+        ImageEvent(int type, int stripIndex) {
+            this.type = type;
+            this.stripIndex = stripIndex;
+        }
+    }
 
-        return true;
+    private static class IfdEvent {
+        int ifd;
+        boolean isRequested;
+        IfdEvent(int ifd, boolean isInterestedIfd) {
+            this.ifd = ifd;
+            this.isRequested = isInterestedIfd;
+        }
+    }
+
+    private static class ExifTagEvent {
+        ExifTag tag;
+        boolean isRequested;
+        ExifTagEvent(ExifTag tag, boolean isRequireByUser) {
+            this.tag = tag;
+            this.isRequested = isRequireByUser;
+        }
     }
 }
\ No newline at end of file
index f406cb7..125b6c6 100644 (file)
@@ -19,132 +19,46 @@ package com.android.gallery3d.exif;
 import java.io.IOException;
 import java.io.InputStream;
 
+/**
+ * This class reads the EXIF header of a JPEG file and stores it in {@link ExifData}.
+ */
 public class ExifReader {
-
+    /**
+     * Parse the inputStream and return all Exif data.
+     * @throws ExifInvalidFormatException
+     * @throws IOException
+     */
     public ExifData getExifData(InputStream inputStream) throws ExifInvalidFormatException,
             IOException {
-        ExifParser parser = new ExifParser();
-        IfdParser ifdParser = parser.parse(inputStream);
+        ExifParser parser = ExifParser.parse(inputStream);
         ExifData exifData = new ExifData();
-        IfdData ifdData = new IfdData(ExifData.TYPE_IFD_0);
-        parseIfd(ifdParser, ifdData, exifData);
-        exifData.addIfdData(ifdData);
-        return exifData;
-    }
 
-    public void parseIfd(IfdParser ifdParser, IfdData ifdData, ExifData exifData)
-            throws IOException, ExifInvalidFormatException {
-        int type = ifdParser.next();
-        while (type != IfdParser.TYPE_END) {
-            switch (type) {
-                case IfdParser.TYPE_NEW_TAG:
-                    ExifTag tag = ifdParser.readTag();
-                    if (tag.getDataSize() > 4) {
-                        long offset = ifdParser.readUnsignedInt();
-                        ifdParser.waitValueOfTag(tag, offset);
-                    } else if (tag.getTagId() == ExifTag.TIFF_TAG.TAG_EXIF_IFD
-                            || tag.getTagId() == ExifTag.TIFF_TAG.TAG_GPS_IFD
-                            || tag.getTagId() == ExifTag.EXIF_TAG.TAG_INTEROPERABILITY_IFD) {
-                        long offset = ifdParser.readUnsignedInt();
-                        ifdParser.waitValueOfTag(tag, offset);
-                        ifdData.addTag(tag, offset);
+        int event = parser.next();
+        while (event != ExifParser.EVENT_END) {
+            switch (event) {
+                case ExifParser.EVENT_START_OF_IFD:
+                    exifData.addIfdData(new IfdData(parser.getCurrentIfd()));
+                    break;
+                case ExifParser.EVENT_NEW_TAG:
+                    ExifTag tag = parser.getTag();
+                    if (!tag.hasValue()) {
+                        parser.registerForTagValue(tag);
                     } else {
-                        readAndSaveTag(tag, ifdParser, ifdData);
+                        exifData.getIfdData(tag.getIfd()).setTag(tag);
                     }
                     break;
-                case IfdParser.TYPE_NEXT_IFD:
-                    IfdData ifd1 = new IfdData(ExifData.TYPE_IFD_1);
-                    parseIfd(ifdParser.parseIfdBlock(), ifd1, exifData);
-                    exifData.addIfdData(ifd1);
-                    break;
-                case IfdParser.TYPE_VALUE_OF_PREV_TAG:
-                    tag = ifdParser.getCorrespodingExifTag();
-                    if(tag.getTagId() == ExifTag.TIFF_TAG.TAG_EXIF_IFD) {
-                        IfdData ifd = new IfdData(ExifData.TYPE_IFD_EXIF);
-                        parseIfd(ifdParser.parseIfdBlock(), ifd, exifData);
-                        exifData.addIfdData(ifd);
-                    } else if(tag.getTagId() == ExifTag.TIFF_TAG.TAG_GPS_IFD) {
-                        IfdData ifd = new IfdData(ExifData.TYPE_IFD_GPS);
-                        parseIfd(ifdParser.parseIfdBlock(), ifd, exifData);
-                        exifData.addIfdData(ifd);
-                    } else if(tag.getTagId() == ExifTag.EXIF_TAG.TAG_INTEROPERABILITY_IFD) {
-                        IfdData ifd = new IfdData(ExifData.TYPE_IFD_INTEROPERABILITY);
-                        parseIfd(ifdParser.parseIfdBlock(), ifd, exifData);
-                        exifData.addIfdData(ifd);
-                    } else {
-                        readAndSaveTag(tag, ifdParser, ifdData);
+                case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+                    tag = parser.getTag();
+                    if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) {
+                        byte[] buf = new byte[tag.getComponentCount()];
+                        parser.read(buf);
+                        tag.setValue(buf);
                     }
+                    exifData.getIfdData(tag.getIfd()).setTag(tag);
                     break;
             }
-            type = ifdParser.next();
-        }
-    }
-
-    public void readAndSaveTag(ExifTag tag, IfdParser parser, IfdData ifdData)
-            throws IOException {
-        switch(tag.getDataType()) {
-            case ExifTag.TYPE_BYTE:
-            {
-                byte buf[] = new byte[tag.getComponentCount()];
-                parser.read(buf);
-                ifdData.addTag(tag, buf);
-                break;
-            }
-            case ExifTag.TYPE_ASCII:
-                ifdData.addTag(tag, parser.readString(tag.getComponentCount()));
-                break;
-            case ExifTag.TYPE_INT:
-            {
-                long value[] = new long[tag.getComponentCount()];
-                for (int i = 0, n = value.length; i < n; i++) {
-                    value[i] = parser.readUnsignedInt();
-                }
-                ifdData.addTag(tag, value);
-                break;
-            }
-            case ExifTag.TYPE_RATIONAL:
-            {
-                Rational value[] = new Rational[tag.getComponentCount()];
-                for (int i = 0, n = value.length; i < n; i++) {
-                    value[i] = parser.readUnsignedRational();
-                }
-                ifdData.addTag(tag, value);
-                break;
-            }
-            case ExifTag.TYPE_SHORT:
-            {
-                int value[] = new int[tag.getComponentCount()];
-                for (int i = 0, n = value.length; i < n; i++) {
-                    value[i] = parser.readUnsignedShort();
-                }
-                ifdData.addTag(tag, value);
-                break;
-            }
-            case ExifTag.TYPE_SINT:
-            {
-                int value[] = new int[tag.getComponentCount()];
-                for (int i = 0, n = value.length; i < n; i++) {
-                    value[i] = parser.readInt();
-                }
-                ifdData.addTag(tag, value);
-                break;
-            }
-            case ExifTag.TYPE_SRATIONAL:
-            {
-                Rational value[] = new Rational[tag.getComponentCount()];
-                for (int i = 0, n = value.length; i < n; i++) {
-                    value[i] = parser.readRational();
-                }
-                ifdData.addTag(tag, value);
-                break;
-            }
-            case ExifTag.TYPE_UNDEFINED:
-            {
-                byte buf[] = new byte[tag.getComponentCount()];
-                parser.read(buf);
-                ifdData.addTag(tag, buf);
-                break;
-            }
+            event = parser.next();
         }
+        return exifData;
     }
-}
+}
\ No newline at end of file
index c11171c..7b5a2eb 100644 (file)
 
 package com.android.gallery3d.exif;
 
+import java.lang.reflect.Array;
+
+/**
+ * This class stores information of an EXIF tag.
+ * @see ExifParser
+ * @see ExifReader
+ * @see IfdData
+ * @see ExifData
+ */
 public class ExifTag {
     public static interface TIFF_TAG {
         public static final short TAG_IMAGE_WIDTH = 0x100;
@@ -315,54 +324,259 @@ public class ExifTag {
         public static final short INTEROPERABILITY_INDEX = 1;
     }
 
-    public static final short TYPE_BYTE = 1;
+    public static final short TYPE_UNSIGNED_BYTE = 1;
     public static final short TYPE_ASCII = 2;
-    public static final short TYPE_SHORT = 3;
-    public static final short TYPE_INT = 4;
-    public static final short TYPE_RATIONAL = 5;
+    public static final short TYPE_UNSIGNED_SHORT = 3;
+    public static final short TYPE_UNSIGNED_INT = 4;
+    public static final short TYPE_UNSIGNED_RATIONAL = 5;
     public static final short TYPE_UNDEFINED = 7;
-    public static final short TYPE_SINT = 9;
-    public static final short TYPE_SRATIONAL = 10;
+    public static final short TYPE_INT = 9;
+    public static final short TYPE_RATIONAL = 10;
 
     private static final int TYPE_TO_SIZE_MAP[] = new int[11];
     static {
-        TYPE_TO_SIZE_MAP[TYPE_BYTE] = 1;
+        TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE] = 1;
         TYPE_TO_SIZE_MAP[TYPE_ASCII] = 1;
-        TYPE_TO_SIZE_MAP[TYPE_SHORT] = 2;
+        TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_SHORT] = 2;
+        TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_INT] = 4;
+        TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_RATIONAL] = 8;
+        TYPE_TO_SIZE_MAP[TYPE_UNDEFINED] = 1;
         TYPE_TO_SIZE_MAP[TYPE_INT] = 4;
         TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8;
-        TYPE_TO_SIZE_MAP[TYPE_UNDEFINED] = 1;
-        TYPE_TO_SIZE_MAP[TYPE_SINT] = 4;
-        TYPE_TO_SIZE_MAP[TYPE_SRATIONAL] = 8;
     }
 
+    /**
+     * Gets the element size of the given data type.
+     *
+     * @see #TYPE_ASCII
+     * @see #TYPE_INT
+     * @see #TYPE_RATIONAL
+     * @see #TYPE_UNDEFINED
+     * @see #TYPE_UNSIGNED_BYTE
+     * @see #TYPE_UNSIGNED_INT
+     * @see #TYPE_UNSIGNED_RATIONAL
+     * @see #TYPE_UNSIGNED_SHORT
+     */
     public static int getElementSize(short type) {
         return TYPE_TO_SIZE_MAP[type];
     }
 
     private final short mTagId;
     private final short mDataType;
-    private final int mDataCount;
+    private final int mComponentCount;
+    private final int mIfd;
+    private Object mValue;
+    private int mOffset;
 
-    ExifTag(short tagId, short type, int dataCount) {
+    ExifTag(short tagId, short type, int componentCount, int ifd) {
         mTagId = tagId;
         mDataType = type;
-        mDataCount = dataCount;
+        mComponentCount = componentCount;
+        mIfd = ifd;
     }
 
+    /**
+     * Returns the ID of the IFD this tag belongs to.
+     *
+     * @see IfdId#TYPE_IFD_0
+     * @see IfdId#TYPE_IFD_1
+     * @see IfdId#TYPE_IFD_EXIF
+     * @see IfdId#TYPE_IFD_GPS
+     * @see IfdId#TYPE_IFD_INTEROPERABILITY
+     */
+    public int getIfd() {
+        return mIfd;
+    }
+
+    /**
+     * Gets the ID of this tag.
+     */
     public short getTagId() {
         return mTagId;
     }
 
+    /**
+     * Gets the data type of this tag
+     *
+     * @see #TYPE_ASCII
+     * @see #TYPE_INT
+     * @see #TYPE_RATIONAL
+     * @see #TYPE_UNDEFINED
+     * @see #TYPE_UNSIGNED_BYTE
+     * @see #TYPE_UNSIGNED_INT
+     * @see #TYPE_UNSIGNED_RATIONAL
+     * @see #TYPE_UNSIGNED_SHORT
+     */
     public short getDataType() {
         return mDataType;
     }
 
+    /**
+     * Gets the total data size in bytes of the value of this tag.
+     */
     public int getDataSize() {
         return getComponentCount() * getElementSize(getDataType());
     }
 
+    /**
+     * Gets the component count of this tag.
+     */
     public int getComponentCount() {
-        return mDataCount;
+        return mComponentCount;
+    }
+
+    /**
+     * Returns true if this ExifTag contains value; otherwise, this tag will contain an offset value
+     * that links to the area where the actual value is located.
+     *
+     * @see #getOffset()
+     */
+    public boolean hasValue() {
+        return mValue != null;
+    }
+
+    /**
+     * Gets the offset of this tag. This is only valid if this data size > 4 and contains an offset
+     * to the location of the actual value.
+     */
+    public int getOffset() {
+        return mOffset;
+    }
+
+    /**
+     * Sets the offset of this tag.
+     */
+    void setOffset(int offset) {
+        mOffset = offset;
+    }
+
+    /**
+     * Sets the value of this tag. This is useful when we want to modify the tags and write it back
+     * to the JPEG file
+     */
+    public void setValue(Object object) {
+        if (object.getClass().isArray()) {
+            assert(mComponentCount == Array.getLength(object));
+            mValue = object;
+        } else if (object instanceof String) {
+            assert(mComponentCount == ((String) object).length() + 1);
+            mValue = object;
+        } else {
+            // Wrap object with an array because user may try to get object by get method with
+            // index 0 when size = 1
+            // e.g. getShort(0)
+            assert(mComponentCount == 1);
+            Object array = Array.newInstance(object.getClass(), 1);
+            Array.set(array, 0, object);
+            mValue = array;
+        }
+    }
+
+    public short getShort(int index) {
+        return (Short) Array.get(mValue, index);
+    }
+
+    public short getShort() {
+        return (Short) Array.get(mValue, 0);
+    }
+
+    public int getUnsignedShort(int index) {
+        return (Integer) Array.get(mValue, index);
+    }
+
+    public int getUnsignedShort() {
+        return (Integer) Array.get(mValue, 0);
+    }
+
+    public int getInt(int index) {
+        return (Integer) Array.get(mValue, index);
+    }
+
+    public int getInt() {
+        return (Integer) Array.get(mValue, 0);
+    }
+
+    public long getUnsignedInt(int index) {
+        return (Long) Array.get(mValue, index);
+    }
+
+    public long getUnsignedInt() {
+        return (Long) Array.get(mValue, 0);
+    }
+
+    public String getString() {
+        return (String) mValue;
+    }
+
+    public Rational getRational(int index) {
+        return ((Rational[]) mValue)[index];
+    }
+
+    public Rational getRational() {
+        return ((Rational[]) mValue)[0];
+    }
+
+    public int getBytes(byte[] buf) {
+        return getBytes(buf, 0, buf.length);
+    }
+
+    public int getBytes(byte[] buf, int offset, int length) {
+        byte[] data = (byte[]) mValue;
+        if (data.length < length + offset) {
+            System.arraycopy(data, offset, buf, 0, data.length - offset);
+            return data.length - offset;
+        } else {
+            System.arraycopy(data, offset, buf, 0, length);
+            return length;
+        }
+    }
+
+    /**
+     * Returns a string representation of the value of this tag.
+     */
+    public String valueToString() {
+        StringBuilder sbuilder = new StringBuilder();
+        switch(getDataType()) {
+            case ExifTag.TYPE_UNDEFINED:
+            case ExifTag.TYPE_UNSIGNED_BYTE:
+                byte buf[] = new byte[getComponentCount()];
+                getBytes(buf);
+                for(int i = 0; i < getComponentCount(); i++) {
+                    if(i != 0) sbuilder.append(" ");
+                    sbuilder.append(String.format("%02x", buf[i]));
+                }
+                break;
+            case ExifTag.TYPE_ASCII:
+                // trim the string for comparison between xml
+                sbuilder.append(getString().trim());
+                break;
+            case ExifTag.TYPE_UNSIGNED_INT:
+                for(int i = 0; i < getComponentCount(); i++) {
+                    if(i != 0) sbuilder.append(" ");
+                    sbuilder.append(getUnsignedInt(i));
+                }
+                break;
+            case ExifTag.TYPE_RATIONAL:
+            case ExifTag.TYPE_UNSIGNED_RATIONAL:
+                for(int i = 0; i < getComponentCount(); i++) {
+                    Rational r = getRational(i);
+                    if(i != 0) sbuilder.append(" ");
+                    sbuilder.append(r.getNominator()).append("/").append(r.getDenominator());
+                }
+                break;
+            case ExifTag.TYPE_UNSIGNED_SHORT:
+                for(int i = 0; i < getComponentCount(); i++) {
+                    if(i != 0) sbuilder.append(" ");
+                    sbuilder.append(getUnsignedShort(i));
+                }
+                break;
+            case ExifTag.TYPE_INT:
+                for(int i = 0; i < getComponentCount(); i++) {
+                    if(i != 0) sbuilder.append(" ");
+                    sbuilder.append(getInt(i));
+                }
+                break;
+        }
+        return sbuilder.toString();
     }
 }
\ No newline at end of file
index b0d0630..4747c3c 100644 (file)
 
 package com.android.gallery3d.exif;
 
-import java.lang.reflect.Array;
 import java.util.HashMap;
 import java.util.Map;
 
+/**
+ * This class stores all the tags in an IFD.
+ *
+ * @see ExifData
+ * @see ExifTag
+ */
 public class IfdData {
 
-    private final int mIfdType;
+    private final int mIfdId;
     private final Map<Short, ExifTag> mExifTags = new HashMap<Short, ExifTag>();
-    private final Map<Short, Object> mValues = new HashMap<Short, Object>();
-
-    public IfdData(int ifdType) {
-        mIfdType = ifdType;
-    }
 
+    /**
+     * Creates an IfdData with given IFD ID.
+     *
+     * @see IfdId#TYPE_IFD_0
+     * @see IfdId#TYPE_IFD_1
+     * @see IfdId#TYPE_IFD_EXIF
+     * @see IfdId#TYPE_IFD_GPS
+     * @see IfdId#TYPE_IFD_INTEROPERABILITY
+     */
+    public IfdData(int ifdId) {
+        mIfdId = ifdId;
+    }
+
+    /**
+     * Get a array the contains all {@link ExifTag} in this IFD.
+     */
     public ExifTag[] getAllTags(ExifTag[] outTag) {
         return mExifTags.values().toArray(outTag);
     }
 
-    public int getIfdType() {
-        return mIfdType;
+    /**
+     * Gets the ID of this IFD.
+     *
+     * @see IfdId#TYPE_IFD_0
+     * @see IfdId#TYPE_IFD_1
+     * @see IfdId#TYPE_IFD_EXIF
+     * @see IfdId#TYPE_IFD_GPS
+     * @see IfdId#TYPE_IFD_INTEROPERABILITY
+     */
+    public int getId() {
+        return mIfdId;
     }
 
+    /**
+     * Gets the {@link ExifTag} with given tag id. Return null if there is no such tag.
+     */
     public ExifTag getTag(short tagId) {
         return mExifTags.get(tagId);
     }
 
-    public short getShort(short tagId, int index) {
-        return (Short) Array.get(mValues.get(tagId), index);
-    }
-
-    public short getShort(short tagId) {
-        return (Short) Array.get(mValues.get(tagId), 0);
-    }
-
-    public int getUnsignedShort(short tagId, int index) {
-        return (Integer) Array.get(mValues.get(tagId), index);
-    }
-
-    public int getUnsignedShort(short tagId) {
-        return (Integer) Array.get(mValues.get(tagId), 0);
-    }
-
-    public int getInt(short tagId, int index) {
-        return (Integer) Array.get(mValues.get(tagId), index);
-    }
-
-    public int getInt(short tagId) {
-        return (Integer) Array.get(mValues.get(tagId), 0);
-    }
-
-    public long getUnsignedInt(short tagId, int index) {
-        return (Long) Array.get(mValues.get(tagId), index);
-    }
-
-    public long getUnsignedInt(short tagId) {
-        return (Long) Array.get(mValues.get(tagId), 0);
-    }
-
-    public String getString(short tagId) {
-        return (String) mValues.get(tagId);
-    }
-
-    public Rational getRational(short tagId, int index) {
-        return ((Rational[]) mValues.get(tagId))[index];
-    }
-
-    public Rational getRational(short tagId) {
-        return ((Rational[]) mValues.get(tagId))[0];
-    }
-
-    public int getBytes(short tagId, byte[] buf) {
-        return getBytes(tagId, buf, 0, buf.length);
-    }
-
-    public int getBytes(short tagId, byte[] buf, int offset, int length) {
-        Object data = mValues.get(tagId);
-        if (Array.getLength(data) < length + offset) {
-            System.arraycopy(data, offset, buf, 0, Array.getLength(data) - offset);
-            return Array.getLength(data) - offset;
-        } else {
-            System.arraycopy(data, offset, buf, 0, length);
-            return length;
-        }
-    }
-
-    public void addTag(ExifTag tag, Object object) {
-        mExifTags.put(tag.getTagId(),  tag);
-        if (object.getClass().isArray() || object.getClass() == String.class) {
-            mValues.put(tag.getTagId(), object);
-        } else {
-            Object array = Array.newInstance(object.getClass(), 1);
-            Array.set(array, 0, object);
-            mValues.put(tag.getTagId(), array);
-        }
+    /**
+     * Adds or replaces a {@link ExifTag}.
+     */
+    public void setTag(ExifTag tag) {
+        mExifTags.put(tag.getTagId(), tag);
     }
-}
+}
\ No newline at end of file
diff --git a/src/com/android/gallery3d/exif/IfdId.java b/src/com/android/gallery3d/exif/IfdId.java
new file mode 100644 (file)
index 0000000..1b96343
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.gallery3d.exif;
+
+public interface IfdId {
+    public static final int TYPE_IFD_0 = 0;
+    public static final int TYPE_IFD_1 = 1;
+    public static final int TYPE_IFD_EXIF = 2;
+    public static final int TYPE_IFD_INTEROPERABILITY = 3;
+    public static final int TYPE_IFD_GPS = 4;
+    /* This is use in ExifData to allocate enough IfdData */
+    static final int TYPE_IFD_COUNT = 5;
+}
diff --git a/src/com/android/gallery3d/exif/IfdParser.java b/src/com/android/gallery3d/exif/IfdParser.java
deleted file mode 100644 (file)
index 0d1059c..0000000
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.exif;
-
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.util.Map.Entry;
-import java.util.TreeMap;
-
-public class IfdParser {
-
-    private static final int TAG_SIZE = 12;
-
-    private final TiffInputStream mTiffStream;
-    private final int mEndOfTagOffset;
-    private final int mNumOfTag;
-    private int mNextOffset;
-    private int mOffsetToNextIfd = 0;
-
-    private TreeMap<Integer, ExifTag> mCorrespondingTag = new TreeMap<Integer, ExifTag>();
-
-    private ExifTag mCurrTag;
-    private int mCurrTagOffset;
-
-    public static final int TYPE_NEW_TAG = 0;
-    public static final int TYPE_VALUE_OF_PREV_TAG = 1;
-    public static final int TYPE_NEXT_IFD = 2;
-    public static final int TYPE_END = 3;
-
-    IfdParser(TiffInputStream tiffStream, int offset) throws IOException {
-        mTiffStream = tiffStream;
-        mTiffStream.skipTo(offset);
-        mNumOfTag = mTiffStream.readUnsignedShort();
-        mEndOfTagOffset = offset + mNumOfTag * TAG_SIZE + 2;
-        mNextOffset = offset + 2;
-    }
-
-    public int next() throws IOException {
-        int offset = mTiffStream.getReadByteCount();
-        if (offset < mEndOfTagOffset) {
-            offset = mNextOffset;
-            skipTo(mNextOffset);
-
-            if(mNextOffset < mEndOfTagOffset) {
-                mNextOffset += TAG_SIZE;
-                return TYPE_NEW_TAG;
-            }
-        }
-
-        if (offset == mEndOfTagOffset) {
-            mOffsetToNextIfd = mTiffStream.readInt();
-        }
-
-        if (!mCorrespondingTag.isEmpty()) {
-            Entry<Integer, ExifTag> entry = mCorrespondingTag.pollFirstEntry();
-            mCurrTag = entry.getValue();
-            mCurrTagOffset = entry.getKey();
-            skipTo(entry.getKey());
-            return TYPE_VALUE_OF_PREV_TAG;
-        } else {
-            if (offset <= mOffsetToNextIfd) {
-                skipTo(mOffsetToNextIfd);
-                // Reset mOffsetToNextIfd to 0 so next call to next() will point to the end
-                mOffsetToNextIfd = 0;
-                return TYPE_NEXT_IFD;
-            } else {
-                return TYPE_END;
-            }
-        }
-    }
-
-    public ExifTag readTag() throws IOException, ExifInvalidFormatException {
-        short tagId = mTiffStream.readShort();
-        short dataFormat = mTiffStream.readShort();
-        long numOfComp = mTiffStream.readUnsignedInt();
-        if (numOfComp > Integer.MAX_VALUE) {
-            throw new ExifInvalidFormatException(
-                    "Number of component is larger then Integer.MAX_VALUE");
-        }
-        return new ExifTag(tagId, dataFormat, (int) numOfComp);
-    }
-
-    public ExifTag getCorrespodingExifTag() {
-        return mCurrTagOffset != mTiffStream.getReadByteCount() ? null : mCurrTag;
-    }
-
-    public void waitValueOfTag(ExifTag tag, long offset) {
-        if (offset > Integer.MAX_VALUE || offset < 0) {
-            throw new IllegalArgumentException(offset + " must be in 0 ~ " + Integer.MAX_VALUE);
-        }
-        mCorrespondingTag.put((int) offset, tag);
-    }
-
-    public void skipTo(int offset) throws IOException {
-        mTiffStream.skipTo(offset);
-        while (!mCorrespondingTag.isEmpty() && mCorrespondingTag.firstKey() < offset) {
-            mCorrespondingTag.pollFirstEntry();
-        }
-    }
-
-    public IfdParser parseIfdBlock() throws IOException {
-        return new IfdParser(mTiffStream, mTiffStream.getReadByteCount());
-    }
-
-    public int read(byte[] buffer, int offset, int length) throws IOException {
-        return mTiffStream.read(buffer, offset, length);
-    }
-
-    public int read(byte[] buffer) throws IOException {
-        return mTiffStream.read(buffer);
-    }
-
-    public String readString(int n) throws IOException {
-        if (n > 0) {
-            byte[] buf = new byte[n];
-            mTiffStream.readOrThrow(buf);
-            return new String(buf, 0, n - 1, "UTF8");
-        } else {
-            return "";
-        }
-    }
-
-    public String readString(int n, Charset charset) throws IOException {
-        byte[] buf = new byte[n];
-        mTiffStream.readOrThrow(buf);
-        return new String(buf, 0, n - 1, charset);
-    }
-
-    public int readUnsignedShort() throws IOException {
-        return readShort() & 0xffff;
-    }
-
-    public long readUnsignedInt() throws IOException {
-        return readInt() & 0xffffffffL;
-    }
-
-    public Rational readUnsignedRational() throws IOException {
-        long nomi = readUnsignedInt();
-        long denomi = readUnsignedInt();
-        return new Rational(nomi, denomi);
-    }
-
-    public int readInt() throws IOException {
-        return mTiffStream.readInt();
-    }
-
-    public short readShort() throws IOException {
-        return mTiffStream.readShort();
-    }
-
-    public Rational readRational() throws IOException {
-        int nomi = readInt();
-        int denomi = readInt();
-        return new Rational(nomi, denomi);
-    }
-}
\ No newline at end of file
index cef6c91..4c3c77f 100644 (file)
@@ -33,4 +33,4 @@ public class Rational {
     public long getDenominator() {
         return mDenominator;
     }
-}
+}
\ No newline at end of file
index 86ef12f..241aaf5 100644 (file)
@@ -20,7 +20,6 @@ import android.content.res.XmlResourceParser;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.test.InstrumentationTestCase;
-import android.util.Log;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -58,338 +57,176 @@ public class ExifParserTest extends InstrumentationTestCase {
     }
 
     public void testParse() throws IOException, ExifInvalidFormatException {
-        ExifParser parser = new ExifParser();
-        parseIfd0(parser.parse(mImageInputStream));
-    }
-
-    private void parseIfd0(IfdParser ifdParser) throws IOException,
-            ExifInvalidFormatException {
-        int type = ifdParser.next();
-        int tagNumber=0;
-        boolean isEnterNextIfd = false;
-        boolean isEnterExifIfd = false;
-        while (type != IfdParser.TYPE_END) {
-            switch (type) {
-                case IfdParser.TYPE_NEW_TAG:
-                    ExifTag tag = ifdParser.readTag();
-                    if (tag.getDataSize() > 4 || tag.getTagId() == ExifTag.TIFF_TAG.TAG_EXIF_IFD) {
-                        long offset = ifdParser.readUnsignedInt();
-                        assertTrue(offset <= Integer.MAX_VALUE);
-                        ifdParser.waitValueOfTag(tag, offset);
+        ExifParser parser = ExifParser.parse(mImageInputStream);
+        int event = parser.next();
+        while (event != ExifParser.EVENT_END) {
+            switch (event) {
+                case ExifParser.EVENT_START_OF_IFD:
+                    break;
+                case ExifParser.EVENT_NEW_TAG:
+                    ExifTag tag = parser.getTag();
+                    if (!tag.hasValue()) {
+                        parser.registerForTagValue(tag);
                     } else {
-                        checkTag(tag, ifdParser, mIfd0Value);
+                        checkTag(tag);
                     }
-                    tagNumber++;
                     break;
-                case IfdParser.TYPE_NEXT_IFD:
-                    parseIfd1(ifdParser.parseIfdBlock());
-                    isEnterNextIfd = true;
-                    break;
-                case IfdParser.TYPE_VALUE_OF_PREV_TAG:
-                    tag = ifdParser.getCorrespodingExifTag();
-                    if(tag.getTagId() == ExifTag.TIFF_TAG.TAG_EXIF_IFD) {
-                        parseExifIfd(ifdParser.parseIfdBlock());
-                        isEnterExifIfd = true;
-                    } else {
-                        checkTag(ifdParser.getCorrespodingExifTag(), ifdParser, mIfd0Value);
+                case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+                    tag = parser.getTag();
+                    if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) {
+                        byte[] buf = new byte[tag.getComponentCount()];
+                        parser.read(buf);
+                        tag.setValue(buf);
                     }
+                    checkTag(tag);
                     break;
             }
-            type = ifdParser.next();
+            event = parser.next();
         }
-        assertEquals(mIfd0Value.size(), tagNumber);
-        assertTrue(isEnterNextIfd);
-        assertTrue(isEnterExifIfd);
     }
 
-    private void parseIfd1(IfdParser ifdParser) throws IOException,
-            ExifInvalidFormatException {
-        int type = ifdParser.next();
-        int tagNumber = 0;
-        while (type != IfdParser.TYPE_END) {
-            switch (type) {
-                case IfdParser.TYPE_NEW_TAG:
-                    ExifTag tag = ifdParser.readTag();
-                    if (tag.getDataSize() > 4) {
-                        long offset = ifdParser.readUnsignedInt();
-                        assertTrue(offset <= Integer.MAX_VALUE);
-                        ifdParser.waitValueOfTag(tag, offset);
-                    } else {
-                        checkTag(tag, ifdParser, mIfd1Value);
-                    }
-                    tagNumber++;
-                    break;
-                case IfdParser.TYPE_NEXT_IFD:
-                    fail("Find a ifd after ifd1");
-                    break;
-                case IfdParser.TYPE_VALUE_OF_PREV_TAG:
-                    checkTag(ifdParser.getCorrespodingExifTag(), ifdParser, mIfd1Value);
-                    break;
-            }
-            type = ifdParser.next();
+    private void checkTag(ExifTag tag) {
+        HashMap<Short, String> truth = null;
+        switch (tag.getIfd()) {
+            case IfdId.TYPE_IFD_0:
+                truth = mIfd0Value;
+                break;
+            case IfdId.TYPE_IFD_1:
+                truth = mIfd1Value;
+                break;
+            case IfdId.TYPE_IFD_EXIF:
+                truth = mExifIfdValue;
+                break;
+            case IfdId.TYPE_IFD_INTEROPERABILITY:
+                truth = mInteroperabilityIfdValue;
+                break;
         }
-        assertEquals(mIfd1Value.size(), tagNumber);
-    }
 
-    private void parseExifIfd(IfdParser ifdParser) throws IOException,
-            ExifInvalidFormatException {
-        int type = ifdParser.next();
-        int tagNumber = 0;
-        boolean isHasInterIfd = false;
-        boolean isEnterInterIfd = false;
-        while (type != IfdParser.TYPE_END) {
-            switch (type) {
-                case IfdParser.TYPE_NEW_TAG:
-                    ExifTag tag = ifdParser.readTag();
-                    if (tag.getDataSize() > 4) {
-                        long offset = ifdParser.readUnsignedInt();
-                        assertTrue(offset <= Integer.MAX_VALUE);
-                        ifdParser.waitValueOfTag(tag, offset);
-                    } else if (tag.getTagId() == ExifTag.EXIF_TAG.TAG_INTEROPERABILITY_IFD) {
-                        long offset = ifdParser.readUnsignedInt();
-                        assertTrue(offset <= Integer.MAX_VALUE);
-                        ifdParser.waitValueOfTag(tag, offset);
-                        isHasInterIfd = true;
-                    } else {
-                        checkTag(tag, ifdParser, mExifIfdValue);
-                    }
-                    tagNumber++;
-                    break;
-                case IfdParser.TYPE_NEXT_IFD:
-                    fail("Find a ifd after exif ifd");
-                    break;
-                case IfdParser.TYPE_VALUE_OF_PREV_TAG:
-                    tag = ifdParser.getCorrespodingExifTag();
-                    if (tag.getTagId() == ExifTag.EXIF_TAG.TAG_INTEROPERABILITY_IFD) {
-                        parseInteroperabilityIfd(ifdParser.parseIfdBlock());
-                        isEnterInterIfd = true;
-                    } else {
-                        checkTag(ifdParser.getCorrespodingExifTag(), ifdParser, mExifIfdValue);
-                    }
-                    break;
-            }
-            type = ifdParser.next();
-        }
-        assertEquals(mExifIfdValue.size(), tagNumber);
-        if (isHasInterIfd) {
-            assertTrue(isEnterInterIfd);
+        String truthString = truth.get(tag.getTagId());
+        String dataString = tag.valueToString();
+        if (truthString == null) {
+            fail(String.format("Unknown Tag %02x", tag.getTagId()));
         }
+        assertEquals(String.format("Tag %02x", tag.getTagId()), truthString, dataString);
     }
-    private void parseInteroperabilityIfd(IfdParser ifdParser) throws IOException,
-            ExifInvalidFormatException {
-        int type = ifdParser.next();
-        int tagNumber = 0;
-        while (type != IfdParser.TYPE_END) {
-            switch (type) {
-                case IfdParser.TYPE_NEW_TAG:
-                    ExifTag tag = ifdParser.readTag();
-                    if (tag.getDataSize() > 4) {
-                        long offset = ifdParser.readUnsignedInt();
-                        assertTrue(offset <= Integer.MAX_VALUE);
-                        ifdParser.waitValueOfTag(tag, offset);
+
+    private void parseOneIfd(int ifd, int options, HashMap<Short, String> expectedResult)
+            throws IOException, ExifInvalidFormatException {
+        int numOfTag = 0;
+        ExifParser parser = ExifParser.parse(mImageInputStream, options);
+        int event = parser.next();
+        while(event != ExifParser.EVENT_END) {
+            switch (event) {
+                case ExifParser.EVENT_START_OF_IFD:
+                    assertEquals(ifd, parser.getCurrentIfd());
+                    break;
+                case ExifParser.EVENT_NEW_TAG:
+                    numOfTag++;
+                    ExifTag tag = parser.getTag();
+                    if (tag.hasValue()) {
+                        checkTag(tag);
                     } else {
-                        checkTag(tag, ifdParser, mInteroperabilityIfdValue);
+                        parser.registerForTagValue(tag);
                     }
-                    tagNumber++;
                     break;
-                case IfdParser.TYPE_NEXT_IFD:
-                    fail("Find a ifd after exif ifd");
+                case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+                    tag = parser.getTag();
+                    if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) {
+                        byte[] buf = new byte[tag.getComponentCount()];
+                        parser.read(buf);
+                        tag.setValue(buf);
+                    }
+                    checkTag(tag);
                     break;
-                case IfdParser.TYPE_VALUE_OF_PREV_TAG:
-                    checkTag(ifdParser.getCorrespodingExifTag(), ifdParser
-                            , mInteroperabilityIfdValue);
+                case ExifParser.EVENT_COMPRESSED_IMAGE:
+                case ExifParser.EVENT_UNCOMPRESSED_STRIP:
+                    fail("Invalid Event type: " + event);
                     break;
             }
-            type = ifdParser.next();
+            event = parser.next();
         }
-        assertEquals(mInteroperabilityIfdValue.size(), tagNumber);
+        assertEquals(expectedResult.size(), numOfTag);
     }
 
-    private void checkTag(ExifTag tag, IfdParser ifdParser, HashMap<Short, String> truth)
-            throws IOException {
-        String truthString = truth.get(tag.getTagId());
-        if (truthString == null) {
-            fail(String.format("Unknown Tag %02x", tag.getTagId()));
-        }
-        String dataString = readValueToString(tag, ifdParser);
-        if (!truthString.equals(dataString)) {
-            fail(String.format("Tag %02x: expect %s but %s",
-                    tag.getTagId(), truthString, dataString));
-        }
+    public void testOnlyExifIfd() throws IOException, ExifInvalidFormatException {
+        parseOneIfd(IfdId.TYPE_IFD_EXIF, ExifParser.OPTION_IFD_EXIF, mExifIfdValue);
     }
 
-    private String readValueToString(ExifTag tag, IfdParser parser) throws IOException {
-        StringBuilder sbuilder = new StringBuilder();
-        switch(tag.getDataType()) {
-            case ExifTag.TYPE_BYTE:
-                byte buf[] = new byte[tag.getComponentCount()];
-                parser.read(buf);
-                for(int i = 0; i < tag.getComponentCount(); i++) {
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(String.format("%02x", buf[i]));
-                }
-                break;
-            case ExifTag.TYPE_ASCII:
-                // trim the string for comparison between xml
-                sbuilder.append(parser.readString(tag.getComponentCount()).trim());
-                break;
-            case ExifTag.TYPE_INT:
-                for(int i = 0; i < tag.getComponentCount(); i++) {
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(parser.readUnsignedInt());
-                }
-                break;
-            case ExifTag.TYPE_RATIONAL:
-                for(int i = 0; i < tag.getComponentCount(); i++) {
-                    Rational r = parser.readUnsignedRational();
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(r.getNominator()).append("/").append(r.getDenominator());
-                }
-                break;
-            case ExifTag.TYPE_SHORT:
-                for(int i = 0; i < tag.getComponentCount(); i++) {
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(parser.readUnsignedShort());
-                }
-                break;
-            case ExifTag.TYPE_SINT:
-                for(int i = 0; i < tag.getComponentCount(); i++) {
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(parser.readInt());
-                }
-                break;
-            case ExifTag.TYPE_SRATIONAL:
-                for(int i = 0; i < tag.getComponentCount(); i++) {
-                    Rational r = parser.readRational();
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(r.getNominator()).append("/").append(r.getDenominator());
-                }
-                break;
-            case ExifTag.TYPE_UNDEFINED:
-                byte buffer[] = new byte[tag.getComponentCount()];
-                parser.read(buffer);
-                for(int i = 0; i < tag.getComponentCount(); i++) {
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(String.format("%02x", buffer[i]));
-                }
-                break;
-        }
-        return sbuilder.toString();
+    public void testOnlyIfd0() throws IOException, ExifInvalidFormatException {
+        parseOneIfd(IfdId.TYPE_IFD_0, ExifParser.OPTION_IFD_0, mIfd0Value);
     }
 
-    public void testSkipToNextIfd() throws ExifInvalidFormatException, IOException {
-        ExifParser exifParser = new ExifParser();
-        IfdParser ifdParser = exifParser.parse(mImageInputStream);
-        int type = ifdParser.next();
-        boolean isEnterNextIfd = false;
-        while (type != IfdParser.TYPE_END) {
-            switch (type) {
-                case IfdParser.TYPE_NEW_TAG:
-                    // Do nothing, we don't care
-                    break;
-                case IfdParser.TYPE_NEXT_IFD:
-                    parseIfd1(ifdParser.parseIfdBlock());
-                    isEnterNextIfd = true;
-                    break;
-                case IfdParser.TYPE_VALUE_OF_PREV_TAG:
-                    // We won't get this since to skip everything
-                    fail("Get value of previous tag but we've skip everything");
-                    break;
-            }
-            type = ifdParser.next();
-        }
-        assertTrue(isEnterNextIfd);
+    public void testOnlyIfd1() throws IOException, ExifInvalidFormatException {
+        parseOneIfd(IfdId.TYPE_IFD_1, ExifParser.OPTION_IFD_1, mIfd1Value);
+    }
+
+    public void testOnlyInteroperabilityIfd() throws IOException, ExifInvalidFormatException {
+        parseOneIfd(IfdId.TYPE_IFD_INTEROPERABILITY, ExifParser.OPTION_IFD_INTEROPERABILITY
+                , mInteroperabilityIfdValue);
     }
 
-    public void testOnlySaveSomeValue() throws ExifInvalidFormatException, IOException {
-        ExifParser exifParser = new ExifParser();
-        IfdParser ifdParser = exifParser.parse(mImageInputStream);
-        boolean isEnterNextIfd = false;
-        boolean isEnterExifIfd = false;
-        int type = ifdParser.next();
-        while (type != IfdParser.TYPE_END) {
-            switch (type) {
-                case IfdParser.TYPE_NEW_TAG:
-                    ExifTag tag = ifdParser.readTag();
-                    // only interested in these two tags
-                    if (tag.getDataSize() > 4 || tag.getTagId() == ExifTag.TIFF_TAG.TAG_EXIF_IFD) {
-                        if(tag.getTagId() == ExifTag.TIFF_TAG.TAG_MODEL
-                                || tag.getTagId() == ExifTag.TIFF_TAG.TAG_EXIF_IFD) {
-                            long offset = ifdParser.readUnsignedInt();
-                            assertTrue(offset <= Integer.MAX_VALUE);
-                            ifdParser.waitValueOfTag(tag, offset);
+    public void testOnlyReadSomeTag() throws IOException, ExifInvalidFormatException {
+        ExifParser parser = ExifParser.parse(mImageInputStream, ExifParser.OPTION_IFD_0);
+        int event = parser.next();
+        boolean isTagFound = false;
+        while (event != ExifParser.EVENT_END) {
+            switch (event) {
+                case ExifParser.EVENT_START_OF_IFD:
+                    assertEquals(IfdId.TYPE_IFD_0, parser.getCurrentIfd());
+                    break;
+                case ExifParser.EVENT_NEW_TAG:
+                    ExifTag tag = parser.getTag();
+                    if (tag.getTagId() == ExifTag.TIFF_TAG.TAG_MODEL) {
+                        if (tag.hasValue()) {
+                            isTagFound = true;
+                            checkTag(tag);
+                        } else {
+                            parser.registerForTagValue(tag);
                         }
+                        parser.skipRemainingTagsInCurrentIfd();
                     }
                     break;
-                case IfdParser.TYPE_NEXT_IFD:
-                    parseIfd1(ifdParser.parseIfdBlock());
-                    isEnterNextIfd = true;
-                    break;
-                case IfdParser.TYPE_VALUE_OF_PREV_TAG:
-                    tag = ifdParser.getCorrespodingExifTag();
-                    if(tag.getTagId() == ExifTag.TIFF_TAG.TAG_EXIF_IFD) {
-                        parseExifIfd(ifdParser.parseIfdBlock());
-                        isEnterExifIfd = true;
-                    } else {
-                        checkTag(ifdParser.getCorrespodingExifTag(), ifdParser, mIfd0Value);
-                    }
+                case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+                    tag = parser.getTag();
+                    assertEquals(ExifTag.TIFF_TAG.TAG_MODEL, tag.getTagId());
+                    checkTag(tag);
+                    isTagFound = true;
                     break;
             }
-            type = ifdParser.next();
+            event = parser.next();
         }
-        assertTrue(isEnterNextIfd);
-        assertTrue(isEnterExifIfd);
+        assertTrue(isTagFound);
     }
 
     public void testReadThumbnail() throws ExifInvalidFormatException, IOException {
-        ExifParser exifParser = new ExifParser();
-        IfdParser ifdParser = exifParser.parse(mImageInputStream);
-        int type = ifdParser.next();
-        while (type != IfdParser.TYPE_END && type != IfdParser.TYPE_NEXT_IFD) {
-            type = ifdParser.next();
-        }
-        if (type == IfdParser.TYPE_END) {
-            Log.i(TAG, "No Thumbnail");
-            return;
-        }
-        IfdParser ifd1Parser = ifdParser.parseIfdBlock();
-        int thumbOffset = 0;
-        int thumbSize = 0;
-        boolean isFinishRead = false;
-        while (!isFinishRead) {
-            switch (ifd1Parser.next()) {
-                case IfdParser.TYPE_NEW_TAG:
-                    ExifTag tag = ifd1Parser.readTag();
-                    if (tag.getTagId() == ExifTag.TIFF_TAG.TAG_JPEG_INTERCHANGE_FORMAT) {
-                        long unsignedInt = ifdParser.readUnsignedInt();
-                        assertTrue(unsignedInt <= Integer.MAX_VALUE);
-                        thumbOffset = (int) unsignedInt;
-                    } else if (tag.getTagId() ==
-                            ExifTag.TIFF_TAG.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH) {
-                        long unsignedInt = ifdParser.readUnsignedInt();
-                        assertTrue(unsignedInt <= Integer.MAX_VALUE);
-                        thumbSize = (int) unsignedInt;
-                    } else if (tag.getTagId() == ExifTag.TIFF_TAG.TAG_COMPRESSION) {
-                        if (ifdParser.readUnsignedShort() ==
-                                ExifTag.TIFF_TAG.COMPRESSION_UNCOMPRESSION) {
-                            // This test doesn't apply to uncompression thumbnail.
-                            return;
+        ExifParser parser = ExifParser.parse(mImageInputStream,
+                ExifParser.OPTION_IFD_1 | ExifParser.OPTION_THUMBNAIL);
+
+        int event = parser.next();
+        Bitmap bmp = null;
+        boolean mIsContainCompressedImage = false;
+        while (event != ExifParser.EVENT_END) {
+            switch (event) {
+                case ExifParser.EVENT_NEW_TAG:
+                    ExifTag tag = parser.getTag();
+                    if (tag.getTagId() == ExifTag.TIFF_TAG.TAG_COMPRESSION) {
+                        if (tag.getUnsignedShort() == ExifTag.TIFF_TAG.COMPRESSION_JPEG) {
+                            mIsContainCompressedImage = true;
                         }
                     }
-                    isFinishRead = thumbOffset != 0 && thumbSize != 0;
                     break;
-                case IfdParser.TYPE_END:
-                    fail("No thumbnail information found");
+                case ExifParser.EVENT_COMPRESSED_IMAGE:
+                    int imageSize = parser.getCompressedImageSize();
+                    byte buf[] = new byte[imageSize];
+                    parser.read(buf);
+                    bmp = BitmapFactory.decodeByteArray(buf, 0, imageSize);
                     break;
             }
+            event = parser.next();
+        }
+        if (mIsContainCompressedImage) {
+            assertNotNull(bmp);
         }
-
-        byte buf[] = new byte[thumbSize];
-        ifd1Parser.skipTo(thumbOffset);
-        ifd1Parser.read(buf);
-        Bitmap bmp = BitmapFactory.decodeByteArray(buf, 0, thumbSize);
-        // Check correctly decoded
-        assertTrue(bmp != null);
     }
 
     @Override
@@ -399,4 +236,4 @@ public class ExifParserTest extends InstrumentationTestCase {
         mIfd1Value.clear();
         mExifIfdValue.clear();
     }
-}
+}
\ No newline at end of file
index 9964997..c0c2f9f 100644 (file)
@@ -57,72 +57,25 @@ public class ExifReaderTest extends InstrumentationTestCase {
     public void testRead() throws ExifInvalidFormatException, IOException {
         ExifReader reader = new ExifReader();
         ExifData exifData = reader.getExifData(mImageInputStream);
-        checkIfd(exifData, ExifData.TYPE_IFD_0, mIfd0Value);
-        checkIfd(exifData, ExifData.TYPE_IFD_1, mIfd1Value);
-        checkIfd(exifData, ExifData.TYPE_IFD_EXIF, mExifIfdValue);
-        checkIfd(exifData, ExifData.TYPE_IFD_INTEROPERABILITY, mInteroperabilityIfdValue);
+        checkIfd(exifData.getIfdData(IfdId.TYPE_IFD_0), mIfd0Value);
+        checkIfd(exifData.getIfdData(IfdId.TYPE_IFD_1), mIfd1Value);
+        checkIfd(exifData.getIfdData(IfdId.TYPE_IFD_EXIF), mExifIfdValue);
+        checkIfd(exifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY),
+                mInteroperabilityIfdValue);
     }
 
-    private void checkIfd(ExifData exifData, int ifdType, HashMap<Short, String> ifdValue)
-            throws IOException {
-        IfdData ifd = exifData.getIfdData(ifdType);
+    private void checkIfd(IfdData ifd, HashMap<Short, String> ifdValue) {
         if (ifd == null) {
             assertEquals(0 ,ifdValue.size());
             return;
         }
         ExifTag[] tags = ifd.getAllTags(new ExifTag[0]);
         for (ExifTag tag : tags) {
-            assertEquals(ifdValue.get(tag.getTagId()), readValueToString(tag, ifd));
+            assertEquals(ifdValue.get(tag.getTagId()), tag.valueToString());
         }
         assertEquals(ifdValue.size(), tags.length);
     }
 
-    private String readValueToString(ExifTag tag, IfdData ifdData) throws IOException {
-        StringBuilder sbuilder = new StringBuilder();
-        switch(tag.getDataType()) {
-            case ExifTag.TYPE_UNDEFINED:
-            case ExifTag.TYPE_BYTE:
-                byte buf[] = new byte[tag.getComponentCount()];
-                ifdData.getBytes(tag.getTagId(), buf);
-                for(int i = 0; i < tag.getComponentCount(); i++) {
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(String.format("%02x", buf[i]));
-                }
-                break;
-            case ExifTag.TYPE_ASCII:
-                // trim the string for comparison between xml
-                sbuilder.append(ifdData.getString(tag.getTagId()).trim());
-                break;
-            case ExifTag.TYPE_INT:
-                for(int i = 0; i < tag.getComponentCount(); i++) {
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(ifdData.getUnsignedInt(tag.getTagId(), i));
-                }
-                break;
-            case ExifTag.TYPE_SRATIONAL:
-            case ExifTag.TYPE_RATIONAL:
-                for(int i = 0; i < tag.getComponentCount(); i++) {
-                    Rational r = ifdData.getRational(tag.getTagId(), i);
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(r.getNominator()).append("/").append(r.getDenominator());
-                }
-                break;
-            case ExifTag.TYPE_SHORT:
-                for(int i = 0; i < tag.getComponentCount(); i++) {
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(ifdData.getUnsignedShort(tag.getTagId(), i));
-                }
-                break;
-            case ExifTag.TYPE_SINT:
-                for(int i = 0; i < tag.getComponentCount(); i++) {
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(ifdData.getInt(tag.getTagId(), i));
-                }
-                break;
-        }
-        return sbuilder.toString();
-    }
-
     @Override
     public void tearDown() throws Exception {
         mImageInputStream.close();
index ea748cc..72dd313 100644 (file)
@@ -94,4 +94,4 @@ public class ExifXmlReader {
         data.put(id, value);
         parser.require(XmlPullParser.END_TAG, null, XML_TAG);
     }
-}
+}
\ No newline at end of file