summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEarl Ou <shunhsingou@google.com>2012-08-14 17:50:08 +0800
committerEarl Ou <shunhsingou@google.com>2012-08-24 16:35:19 +0800
commitccd9f271fa2bfa56196aef78d0a87d3b00be2015 (patch)
tree408d139c310851e4a4c65a84e01c0172aa8d3f74
parenta987b12c396ef9b0482c4ca827131db125bf8ef8 (diff)
downloadandroid_packages_apps_Snap-ccd9f271fa2bfa56196aef78d0a87d3b00be2015.tar.gz
android_packages_apps_Snap-ccd9f271fa2bfa56196aef78d0a87d3b00be2015.tar.bz2
android_packages_apps_Snap-ccd9f271fa2bfa56196aef78d0a87d3b00be2015.zip
Re-design ExifParser for some strange Exif header
The previous design failed to parser file with strange IFD position. Therefore we need a new design. Change-Id: I9f008a9259df6ef7d9a5022e126d6f25069e3d00
-rw-r--r--src/com/android/gallery3d/exif/ExifData.java37
-rw-r--r--src/com/android/gallery3d/exif/ExifParser.java653
-rw-r--r--src/com/android/gallery3d/exif/ExifReader.java146
-rw-r--r--src/com/android/gallery3d/exif/ExifTag.java244
-rw-r--r--src/com/android/gallery3d/exif/IfdData.java121
-rw-r--r--src/com/android/gallery3d/exif/IfdId.java26
-rw-r--r--src/com/android/gallery3d/exif/IfdParser.java170
-rw-r--r--src/com/android/gallery3d/exif/Rational.java2
-rw-r--r--tests/src/com/android/gallery3d/exif/ExifParserTest.java419
-rw-r--r--tests/src/com/android/gallery3d/exif/ExifReaderTest.java61
-rw-r--r--tests/src/com/android/gallery3d/exif/ExifXmlReader.java2
11 files changed, 1119 insertions, 762 deletions
diff --git a/src/com/android/gallery3d/exif/ExifData.java b/src/com/android/gallery3d/exif/ExifData.java
index 2c316f701..712698ef4 100644
--- a/src/com/android/gallery3d/exif/ExifData.java
+++ b/src/com/android/gallery3d/exif/ExifData.java
@@ -15,22 +15,33 @@
*/
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
diff --git a/src/com/android/gallery3d/exif/ExifParser.java b/src/com/android/gallery3d/exif/ExifParser.java
index f536a55f4..4249a7241 100644
--- a/src/com/android/gallery3d/exif/ExifParser.java
+++ b/src/com/android/gallery3d/exif/ExifParser.java
@@ -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
diff --git a/src/com/android/gallery3d/exif/ExifReader.java b/src/com/android/gallery3d/exif/ExifReader.java
index f406cb739..125b6c65e 100644
--- a/src/com/android/gallery3d/exif/ExifReader.java
+++ b/src/com/android/gallery3d/exif/ExifReader.java
@@ -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
diff --git a/src/com/android/gallery3d/exif/ExifTag.java b/src/com/android/gallery3d/exif/ExifTag.java
index c11171c79..7b5a2eb6b 100644
--- a/src/com/android/gallery3d/exif/ExifTag.java
+++ b/src/com/android/gallery3d/exif/ExifTag.java
@@ -16,6 +16,15 @@
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
diff --git a/src/com/android/gallery3d/exif/IfdData.java b/src/com/android/gallery3d/exif/IfdData.java
index b0d063023..4747c3cce 100644
--- a/src/com/android/gallery3d/exif/IfdData.java
+++ b/src/com/android/gallery3d/exif/IfdData.java
@@ -16,99 +16,64 @@
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
index 000000000..1b9634369
--- /dev/null
+++ b/src/com/android/gallery3d/exif/IfdId.java
@@ -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
index 0d1059cb0..000000000
--- a/src/com/android/gallery3d/exif/IfdParser.java
+++ /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
diff --git a/src/com/android/gallery3d/exif/Rational.java b/src/com/android/gallery3d/exif/Rational.java
index cef6c9112..4c3c77ffb 100644
--- a/src/com/android/gallery3d/exif/Rational.java
+++ b/src/com/android/gallery3d/exif/Rational.java
@@ -33,4 +33,4 @@ public class Rational {
public long getDenominator() {
return mDenominator;
}
-}
+} \ No newline at end of file
diff --git a/tests/src/com/android/gallery3d/exif/ExifParserTest.java b/tests/src/com/android/gallery3d/exif/ExifParserTest.java
index 86ef12fa7..241aaf575 100644
--- a/tests/src/com/android/gallery3d/exif/ExifParserTest.java
+++ b/tests/src/com/android/gallery3d/exif/ExifParserTest.java
@@ -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
diff --git a/tests/src/com/android/gallery3d/exif/ExifReaderTest.java b/tests/src/com/android/gallery3d/exif/ExifReaderTest.java
index 9964997f2..c0c2f9f81 100644
--- a/tests/src/com/android/gallery3d/exif/ExifReaderTest.java
+++ b/tests/src/com/android/gallery3d/exif/ExifReaderTest.java
@@ -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();
diff --git a/tests/src/com/android/gallery3d/exif/ExifXmlReader.java b/tests/src/com/android/gallery3d/exif/ExifXmlReader.java
index ea748cc18..72dd31373 100644
--- a/tests/src/com/android/gallery3d/exif/ExifXmlReader.java
+++ b/tests/src/com/android/gallery3d/exif/ExifXmlReader.java
@@ -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